Call action method from view to automatically populate form value - asp.net-core

I have a form which is used to create a resource, and it has two fields to be populated. When a user enters a value into the first field, I want to automatically call back to an action method on the server which will determine the value to use in the second field, without the user having to submit the form.
For example:
Full Name - User enters this value
Username - View calls server with the value specified in Full Name, server calculates value to be used, server passes value back to view, view presents the value.
Is it possible to do this in MVC core, and if so, can you please point the right direction?
I've been reading up on remote validation, and feel that I could probably use (or abuse) it in order to achieve the functionality looking for, but I'd imagine there's a property way to do this.
Any pointers appreciated.

Remote Validation can only do the validation without submitting the form, but It can't assign value to another field. In your case. It's actually very simple. You can use the js onchange event listen to the first field, in the event, use ajax to access the background. and then fill the returned value into the second field in the callback function. Below is a simple test
View:
<span>Full Name</span>
<input type="text" id="FullName" name="FullName" />
<span>User Name</span>
<input type="text" id="UserName" name="UserName" />
#section scripts{
<script>
$("#FullName").on("change", function () {
$.ajax({
type: "post",
url: "/User/GetUserName",
data: {
fullname: $("#FullName").val()
},
success: function (result) {
$("#UserName").val(result);
}
})
})
</script>
}
Controller:
[HttpPost]
public string GetUserName(string fullname)
{
var username = fullname.Split(" ");
return username.First();
}
Result:

#mj1313's answer was extremely helpful, but I didnt like the idea of having some loose Javascript kicking around in my views, especially since I may need to use this same functionality in multiple views.
I ended up converting the script into a global function, and calling it from within the onchange event like so.
JS Function
(in site.js)
function fullNameToUserName (fullNameId, usernameId) {
$.ajax({
type: "get", // GET rather than POST
url: "/User/GetUserNameFromFullName",
data: {
fullname: $(fullNameId).val()
},
success: function (result) {
$(usernameId).val(result);
}
})
}
Action method
[HttpGet("GetUserNameFromFullName")] // GET rather than POST
public IActionResult GetUserNameFromFullName(string fullName)
{
var username = fullName.Split(" ");
return Ok(username.First());
}
Model
public class UserModel
{
public string FullName { get; set; }
public string Username { get; set; }
}
View
#model MvcApp.Models.UserModel
#Html.DisplayNameFor(m => m.FullName)
#Html.TextBoxFor(m => m.FullName, new
{
onchange = $"fullNameToUserName({#Html.IdFor(m => m.FullName)}, {#Html.IdFor(m => m.Username)});"
})
#Html.DisplayNameFor(m => m.Username)
#Html.TextBoxFor(m => m.Username)
The only problem I have with this approach is that the call to fullNameToUserName(fullNameId, usernameId) is not strongly-typed, and if this function is called from multiple views, it's likely to be miss-typed at some point.
While #mj1313's answer was great and pointed me in the right direction, I wasn't fully satisfied with the approach. Personally, I prefer this approach as it's slightly more reusable and keeps my view's more lean.

Related

Vue.js: binding select boxes, but don't want to ajax all the options

Good day. I'm using Vue.js to render an arbitrary number of select elements from the data in a component.
Here's sample JSON data that indicates there are two select elements, each with one or more options.
{
"dropdowns":[
{
"cd":"UG9ydGZvbGlv",
"formname":"sp_filter_UG9ydGZvbGlv",
"nm":"Portfolio",
"selected":"1a",
"options":[
{
"cd":"1a",
"val":"Option 1A"
}
]
},
{
"cd":"UHJvZHVjdCBOYW1l",
"formname":"sp_filter_UHJvZHVjdCBOYW1l",
"nm":"Product Name",
"selected":"2b",
"options":[
{
"cd":"2a",
"val":"Option 2A"
},
{
"cd":"2b",
"val":"Option 2B"
}
]
}
]
}
Here's the template HTML:
<form>
<div v-for="dropdown in dropdowns">
<div v-if="dropdown.availableToView">
<h4>{{dropdown.nm}}</h4>
<select v-model="dropdown.selected" v-on:change="triggerUpdate">
<option value="">(Make a selection)</option>
<option v-for="option in dropdown.options" :value="option.cd">{{option.val}}</option>
</select>
</div>
</div>
</form>
So far so good.
I've got the data loading and Vue is building the dropdowns.
When the user changes any select box (remember there can be an arbitrary number of them), the trigger action needs to submit ALL of the elements in the form via ajax. It sounds like the most correct option is to bind the form fields to the underlying component data, as I've done.
My triggerUpdate looks like this:
methods: {
triggerUpdate: function() {
axios({
method: "post",
url: actionURL,
data: this.dropdowns
})
.then(response => (this.data = response));
}
}
...but this submits the entire dropdowns data element, including all of the options in each select box. It's unnecessary to send all of the options in. I just want to send each field name along with its selected option (i.e. the "value").
I know i could serialize the whole form and make that my ajax payload. But that seems to be making an "end run" around Vue.js. Everyone talks about having your form fields bound to the Vue model...is it then correct to basically ignore the model when making an ajax request whose purpose is to then update the model?
I'm relatively new to Vue.js so I'd appreciate help with what I'm overlooking here. How should I go about sending in the data from the form (a) while using proper Vue.js binding and (b) without sending extraneous data?
Thanks for your time.
If you need to post only the selected values, and you store those in each dropdown's selected property, the sensible approach seems to be just mapping it to a simple array of name/value objects.
Try this (it assumes the name of each field is the formname property, if it isn't you can just replace it):
var submitData = this.dropdowns.map((dropdown) => {
return { name: dropdown.formname, value: dropdown.selected };
});
Then you send submitData in your ajax request.

using must in fluentValidation

I am using FluentValidation for the server side validation. Now I want to call a function using must.
This is the form code snippet :
<form method="post"
asp-controller="Category"
asp-action="SaveSpecification"
role="form"
data-ajax="true"
data-ajax-loading="#Progress"
data-ajax-success="Specification_JsMethod">
<input asp-for="Caption" class="form-control" />
<input type="hidden" asp-for="CategoryId" />
<button class="btn btn-primary" type="submit"></button>
</form>
What changes should I make to the code below to call function SpecificationMustBeUnique ?
public class SpecificationValidator : AbstractValidator<Specification>
{
public SpecificationValidator()
{
RuleFor(x => new { x.CategoryId, x.Caption}).Must(x => SpecificationMustBeUnique(x.CategoryId, x.Caption)).WithMessage("not unique");
}
private bool SpecificationMustBeUnique(int categoryId, string caption)
{
return true / false;
}
}
Tips: 1 - The combination of CategoyId and Caption should be unique
2 - Validation is not done when submitting the form(the validation just not running when submit the form)
The tricky part is deciding which property should be validated when the validation rule applies to a combination of values on different fields. I usually just close my eyes, and point to one of the view model properties and say "this is the property I'll attach the validator to." With very little thought. FluentValidation works best when the validation rules apply to a single property, so it knows which property will display the validation message.
So, just pick CategoryId or Caption and attach the validator to it:
RuleFor(x => x.CategoryId)
.Must(BeUniqueCategoryAndCaption)
.WithMessage("{PropertyName} and Caption must be unique.");
The signature for the BeUniqueCategoryAndCaption method would look like:
private bool BeUniqueCategoryAndCaption(Specification model, int categoryId)
{
return true / false;
}
Note: I guessed that the CategoryId property is an int, but you will need to make sure the categoryId argument to BeUniqueCategoryAndCaption is the same type as the CategoryId property in your view model.

Select dropdown value after post

I was hoping for some guidance on an issue I am having with preserving the value in a dropdownlist after post (razor)
I have a simple page:
#model testContingency.Models.ListByWardDD
#{
ViewBag.Title = "TestDropDowns";
}
<h2>TestDropDowns</h2>
<div>
#Html.DropDownList("HospModel", Model.Hospital, new { #onchange = "ChangeHospital(this.value)" })
#Html.DropDownList("WardModel", Model.Wards)
<script type="text/javascript">
function ChangeHospital(val) {
window.location.href = "/PatientListByWardDD/TestDropDowns?hospID=" + val;
}
</script>
</div>
here's the controller
public ActionResult TestDropDowns(int? hospID)
{
PASInpatientRepository pasRepo = new PASInpatientRepository();
var returnModel = new ListByWardDD();
var HospitalData = pasRepo.GetPatientHospitalsEnum();
returnModel.Hospital = pasRepo.GetHopspitalListItems(HospitalData);
var WardData = pasRepo .GetPatientWardsEnum(hospID);
returnModel.Wards = pasRepo.GetWardListItems(WardData);
ViewBag.HospSearch = hospID;
return View(returnModel);
}
In the controller PASInpatientRepository() communicates with a cache database. It passes back public IEnumerable < SelectListItem > GetHopspitalListItems. It calls stored procedures written within a cache database (same as sql stored procedures in essence). This is all working fine in its own crude way.
The issue I am having is that when I select the dropdownlist #Html.DropDownList("HospModel", Model.Hospital, new { #onchange = "ChangeHospital(this.value)" }) and the controller is called to refresh the Wards dropdown, I want to preserve the value I have selected in the hospital dropdown. I have tried a few different ways, but I admit, I'm a bit stuck. Most examples I found are for strongly typed.
As I mentioned, I'm new to MVC, but any advice on how to solve this issue, or suggestions on improving my code are greatly appreciated.
So I'm not sure what the Hospital property looks like but I'll make the assumption that each one has a unique ID.
Furthermore to bind the posted data to the view model you'll need to use forms in your view. To create the drop down list use the DropDownListFor-Helper. This way the data will be bound back to your Model after submitting the form.
So your view could look something like this
#model testContingency.Models.ListByWardDD
#{
ViewBag.Title = "TestDropDowns";
}
<h2>TestDropDowns</h2>
<div>
#using (Html.BeginForm("TestDropDowns", "YourController", FormMethod.Post))
{
#Html.DropDownListFor(x => x.HospitalID, Model.Hospital)
#Html.DropDownListFor(x => x.WardID, Model.Wards)
<input type="submit" value="send" />
}
</div>
Your ViewModel testContigency.Models.ListByWardDD must have at least the following properties
public class ListByWardDD {
public int HostpitalID { get;set; }
// the value of the SelectListItem-objects should be the hospital ID
public IEnumerable<SelectListItem> Hospital { get;set; }
public int WardID { get;set; }
// the value of the SelectListItem-objects should be the ward ID
public IEnumerable<SelectListItem> Wards { get;set; }
}
Once you post the form (for simplicity I added a button to send the form and left the javascript part out) the method TestDropDowns of your controller (which you need to fill in the BeginForm-Helper) will be called. That method expects expects an object of type ListByWardDD as a parameter and the framework will automatically populate the values for you.
[HttpPost]
public ActionResult TestDropDowns(ListByWardDD viewModel) {
// your code here, viewModel.HospitalID should contain the selected value
}
Note: After submitting the form the properties Hospital and Wards will be empty. If you need to display the form again, you need to repopulate those properties. Otherwise your dropdown lists are empty.
I tried my best to post valid code but I did not compile or test it.

Passing textbox value from View to action without using Html.begin form and Submit button

I have created a texbox. When user give some input in the textbox and click the actionlink below, the value of the textbox will get pass to the actionResult(FWMenu) in the controller. I can not use html.begin form and submit button in the view. And i can not even use [httppost] in my controller.
Is it possible in that way? If yes then please help me how.
I have not used any class in model.
Below is my Controller.
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult FWMenu(string username)
{
return View();
}
}
This is my View.
<div>
#Html.TextBox("txtUserName")
#Html.ActionLink("Login", "FWMenu", new { username = #Html.TextBox("txtUserName") })
</div>
You need to use javascript/jquery to build the url and redirect. From your comments you mentioned you wanted to use a image rather than a button or link, and that you will have multiple items, so assuming you html is
<div>
<input type="text" name="username">
<img class="submit scr=....>
<div>
<script>
var urlBase='#Url.Action("FWMenu");
$('.submit').click(function() {
var userName = $(this).prev('input').val();
location.href = urlBase + '/' + userName;
}
</script>
Side note: No real point using #Html.TextBox("txtUserName") and if you have multiple instance of this it would generate invalid html (duplicate id attributes) and in any case the name of the parameter is username so it would have needed to be #Html.TextBox("username")`

unobtrusive validation not working with dynamic content

I'm having problems trying to get the unobtrusive jquery validation to work with a partial view that is loaded dynamically through an AJAX call.
I've been spending days trying to get this code to work with no luck.
Here's the View:
#model MvcApplication2.Models.test
#using (Html.BeginForm())
{
#Html.ValidationSummary(true);
<div id="res"></div>
<input id="submit" type="submit" value="submit" />
}
The Partial View:
#model MvcApplication2.Models.test
#Html.TextAreaFor(m => m.MyProperty);
#Html.ValidationMessageFor(m => m.MyProperty);
<script type="text/javascript" >
$.validator.unobtrusive.parse(document);
</script>
The Model:
public class test
{
[Required(ErrorMessage= "required field")]
public int MyProperty { get; set; }
}
The Controller:
public ActionResult GetView()
{
return PartialView("Test");
}
and finally, the javascript:
$(doument).ready(function () {
$.ajax({
url: '/test/getview',
success: function (res) {
$("#res").html(res);
$.validator.unobtrusive.parse($("#res"));
}
});
$("#submit").click(function () {
if ($("form").valid()) {
alert('valid');
return true;
} else {
alert('not valid');
return false;
}
});
The validation does not work. Even if I don't fill any information in the texbox, the submit event shows the alert ('valid').
However, if instead of loading dynamically the view, I use #Html.Partial("test", Model) to render the partial View in the main View (and I don't do the AJAX call), then the validation works just fine.
This is probably because if I load the content dynamically, the controls don't exist in the DOM yet. But I do a call to $.validator.unobtrusive.parse($("#res")); which should be enough to let the validator about the newly loaded controls...
Can anyone help ?
If you try to parse a form that is already parsed it won't update
What you could do when you add dynamic element to the form is either
You could remove the form's validation and re validate it like this:
var form = $(formSelector)
.removeData("validator") /* added by the raw jquery.validate plugin */
.removeData("unobtrusiveValidation"); /* added by the jquery unobtrusive plugin*/
$.validator.unobtrusive.parse(form);
Access the form's unobtrusiveValidation data using the jquery data method:
$(form).data('unobtrusiveValidation')
then access the rules collection and add the new elements attributes (which is somewhat complicated).
You can also check out this article on Applying unobtrusive jquery validation to dynamic content in ASP.Net MVC for a plugin used for adding dynamic elements to a form. This plugin uses the 2nd solution.
As an addition to Nadeem Khedr's answer....
If you've loaded a form in to your DOM dynamically and then call
jQuery.validator.unobtrusive.parse(form);
(with the extra bits mentioned) and are then going to submit that form using ajax remember to call
$(form).valid()
which returns true or false (and runs the actual validation) before you submit your form.
Surprisingly, when I viewed this question, the official ASP.NET docs still did not have any info about the unobtrusive parse() method or how to use it with dynamic content. I took the liberty of creating an issue at the docs repo (referencing #Nadeem's original answer) and submitting a pull request to fix it. This information is now visible in the client side validation section of the model validation topic.
add this to your _Layout.cshtml
$(function () {
//parsing the unobtrusive attributes when we get content via ajax
$(document).ajaxComplete(function () {
$.validator.unobtrusive.parse(document);
});
});
test this:
if ($.validator.unobtrusive != undefined) {
$.validator.unobtrusive.parse("form");
}
I got struck in the same problem and nothing worked except this:
$(document).ready(function () {
rebindvalidators();
});
function rebindvalidators() {
var $form = $("#id-of-form");
$form.unbind();
$form.data("validator", null);
$.validator.unobtrusive.parse($form);
$form.validate($form.data("unobtrusiveValidation").options);
}
and add
// Check if the form is valid
var $form = $(this.form);
if (!$form.valid())
return;
where you are trying to save the form.
I was saving the form through Ajax call.
Hope this will help someone.
just copy this code again in end of modal code
<script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
;)