ASP.NET MVC + FluentValidation + PartialView > no client-side validation - asp.net-mvc

I use FluentValidation to validate my ASP.NET MVC 5.1 viewmodels (using the default unobtrusive client-side validation). I register/create my validators via Ninject (no attributes on the viewmodels):
FluentValidationModelValidatorProvider.Configure(x => x.ValidatorFactory = new NinjectValidatorFactory(_kernel));
AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly()).ForEach(match => _kernel.Bind(match.InterfaceType).To(match.ValidatorType));
I have a partial view that is used often throughout the application. This partial view has its own viewmodel. The "main" viewmodel (the one for the entire view) just inherits from this "partial" viewmodel.
public class IndexViewModel : PersonalInfoViewModel { ... }
The same pattern is used on the validators:
public class IndexValidator : PersonalInfoValidator { ... }
This works fine and the server-side validation works as well as the client-side validation on the "main" view. But the client-side validation on the partial view is not triggered (no data-val-* attributes on input fields there).
My "main" view Index.cshtml
#model IndexViewModel
#Html.TextBoxFor(x => x.SomeProperty) // client-side validation works fine
#Html.Partial("PersonalInfo") // client-side validation in the partial view does not work (see below)
My partial view PersonalInfo.cshtml:
#model PersonalInfoViewModel
#Html.TextBoxFor(x => x.FirstName) // client-side validation does not work
I noticed that it works when I change the model for the partial view to the "main" viewmodel:
#model IndexViewModel
#Html.TextBoxFor(x => x.FirstName) // client-side validation works
So I guess somewhere when building the client-side validation the correct validator does not get picked up and no data-val-* attributes are injected into the HTML. But I can't changed the viewmodel on the partial view, because it's used on several different pages with different "main" views (all inherit from PersonalInfoViewModel though).
Any ideas on how to get my client-side validation working in this case?
Update
After a few hours of digging through the ASP.NET WebStack source, I found that the problem seems to occur in the TextBoxFor method. There the ModelMetaData is created from the model - and in there somewhere it starts to use IndexViewModel instead of PersonalInfoViewModel to get the metadata for client-side validation:
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Html.GetUnobtrusiveValidationAttributes("FirstName", metadata); // no client-side validation attributes found
But this generates the correct client-side validation attributes if I don't specify the metadata explicitly:
Html.GetUnobtrusiveValidationAttributes("FirstName");
Unfortunately, I have not found a way to use this knowledge for my advantage so far.

I wasn't able to find a nice solution to my problem, and didn't want to play around with the ASP.NET WebStack source anymore, so I settled with an ugly workaround:
I "manually" inject the missing data-val-* attributes when rendering the input fields, like this:
#Html.TextBoxFor(x => x.FirstName, Html.GetUnobtrusiveValidationAttributes("FirstName"))
Since GetUnobtrusiveValidationAttributes (called stand-alone) builds the correct HTML attributes I just take them and add it the to TextBox. Definitely not beautiful, but it works and I can go on with my work. If somebody has a better solution, I'd be very happy to hear it.

Related

How does Controller get the Model from the View FormMethod.Post?

In my ASP.NET MVC 5.2 application running .NET Framework v4.5.2, my AdminController has an InventoryQueryList method that accepts the model:
[HandleError]
[HttpPost]
public ActionResult InventoryQueryList(CheckInventoryQueryModel model)
{
// ...
}
My view contains the model and calls the InventoryQueryList method on POST:
#model CCNRebuild.Models.CheckInventoryQueryModel
#{
ViewBag.Title = "InventoryQuery";
Layout = "~/Views/Shared/_ConsoleLayout.cshtml";
}
#using (Html.BeginForm("InventoryQueryList", "Admin", FormMethod.Post))
{
#Html.AntiForgeryToken();
<label>
Dealer:#Html.DropDownListFor(m => m.DealerID, Model.Dealerships)
</label>
...
<input type="submit" value="Submit" />
}
But, whenever I click the submit button, I get an error:
MissingMethodException: No parameterless constructor defined for this object.
Why is the view not passing my model parameter to the controller?
The controller has never had a parameterless constructor in the 3 months that I have been working here.
NOTE: I tried to only show relevant code, and I left out anything unnecessary. If there is anything missing that needs to be seen, please comment and I'll try to oblige.
The error is telling you that CheckInventoryQueryModel doesn't have a parameterless constructor, and it needs one. So you would either:
Remove whatever non-parameterless constructor(s) it does have (and update related code accordingly), or
Add a parameterless constructor.
The model binder needs a parameterless constructor in order to construct an instance of the model. (Unless you write a custom model binder for this type. Which probably isn't the road you want to take, but is an option.) This is a fairly common pattern in frameworks that automate model instance creation. Entity Framework, for example.
As for the actual questions being asked...
How does Controller get the Model from the View FormMethod.Post?
and
Why is the view not passing my model parameter to the controller?
It just sounds like you were misinterpreting the error. I see no reason to suggest that the page isn't passing the form value(s) to the server. Though you can always confirm this in your browser's debugging tools by observing the HTTP request being made when posting the form.
The request sends the data to the server, and can do so in a variety of ways. Form posts are generally key/value pairs. The ASP.NET MVC framework's model binder uses those key/value pairs to populate properties on the model. It just couldn't do that in this case because it couldn't create an instance of the model to be populated.

Does MVC remember checkbox values after submit?

I'm a php head and getting to grips with ASP.NET MVC 5.
In php, after submitting checkbox fields, in order to post back the form with the checkboxes you checked initially set to true, you have to run a if isset on em.
However reading up on model binding in mvc, it seems that this is done automatically for you, i.e checkboxes are returned after form submit checked, if originally selected, using either the HTML.CheckBox or HTML.CheckBoxFor helpers.
Is this the case, or am I expecting too much of MVC?
No, ASP.NET MVC doesn't remember checkbox values after they're submitted. Being an HTTP application as soon as ASP.NET MVC has rendered the HTML it ends the request and forgets everything about what it's just done. Then upon submitting a form ASP.NET MVC processes the incoming HTTP request and maps it to your model via its model binding (more on how it does this in a moment).
Having come from a PHP background myself this is one of those questions I always had when starting with ASP.NET MVC.
With ASP.NET MVC you have to remember that you're working within the context of a complete framework, and in order to ensure you're as productive as possible ASP.NET MVC will take care of a lot of the mundane work for you - ASP.NET MVC's model binding is a perfect example of this.
When submitting a form, the ASP.NET MVC framework will parse all incoming post data and attempt to automatically map it to the values you're providing it via your controller action.
So where as in PHP you'd normally do something along the lines of:
if(isset($_POST['checkboxValue'])) {
$checkboxVal = $_POST['checkboxValue'];
}
ASP.NET MVC will automaltically bind the incoming post data to your action parameter like so:
[HttpPost]
public ActionResult Submit(bool checkboxValue){
}
It does this by checking the parameter name (checkboxValue) matches that of the post data array key, and that the type also matches up. For instance, if you were to change the above checkboxValue from a boolean to a string and change the name, then ASP.NET MVC's model binding will be unable to match the property to the post data and will not automatically set the value for you.
It's also worth noting that ASP.NET MVC's model binding doesn't know how you created the checkbox.
The HTML.CheckBox and HTML.CheckBoxFor html helpers are purely a means to make it easier for you to create the HTML. If you were to manually write the HTML yourself then the model binder will still successfully bind the submitted data.
Edit:
As #DrinkBird has quite rightly pointed out, you're also able to access all of your form's post data by using the FormCollection instance like so:
[HttpPost]
public ActionResult Submit(FormCollection postData){
}
This collection represents all of the data posted to the Submit action.
Yes, model-binding should allow you to retrieve the value of a checkbox on submission.
if your model looks like:
public class myModel
{
public bool myBool {get; set;}
}
and in your HTML, you've used the helper
#Html.CheckBoxFor(m => m.myBool)
Then in your post action to handle the submission:
[HttpPost]
public ActionResult MyAction(myModel model)
{
var whatsThis = model.myBool;
}
...whatsThis will be true if the checkbox was checked, false if not.
Part of why this works is that when you use #html.CheckBoxFor, it also places a hidden form field that will pass false if the box is unchecked, to aid with model binding -- if it didn't, per HTTP there would be no varibalbe 'myBool' submitted in the post-vars collection.
If you return this model back into the form (say, if it didn't validate), then the form will re-present the checkbox in whatever state it was in on submission:
[HttpPost]
public ActionResult MyAction(myModel model)
{
if(!ModelState.IsValid)
{
return View(model);
}
else
{
//do success
}
}

Do we have user control and hidden field in razor?

In Asp.net , there is user control, which is an ascx page, and we can have hidden fields:
public partial class classA:System.Web.Mvc.ViewUserControl<Models.classB>
{
//hidden field
public string url
{
get{ ...... }
}
}
But now i am using razor in asp.net mvc3 ,which is cshtml file. Do we have anything corresponding to that? How can i use hidden field now?
MVC views do no have fields like a Web Forms user control.
MVC views (partial views, too) get their data by passing an instance of a Viewmodel class to them. You can indicate the type of the viewmodel class with the #model directive (usually the first line in a view):
#model MyViewmodelClass
<span>#Model.MyClassProperty</span>
The viewmodel class itself could contain private fields like any class.
If you need variables to be used within the view itself, you can simply declare them within a razor code block and use them wwithin the view:
#{
var privatevar = "value";
}
<span>#privatevar</span>
However, as a best practice, the view's responsibility in ASP.NET MVC should be limited to the display of the data and therefore not contain code unrelated to that.
While working with razor it is essential to have basic HTML knowledge as you need to write some html and there is no and Drag and Drop to add controls on the page.
MVC have introduced Partial Views which can be said a alternate/replacement to User Control.
You can create a PartialView by selecting Checkbox stating "Create Partial View" in View Creation Dialog. Secondly In MVC it is not difficult to create partial view mannual as well.
As you are using Razor, just set Layout = null; at the top of your view and it will be treated as partial view. Secondly you can easily make any field as hidden using display property under Style attribute.
Happy Coding :)

Custom EditorTemplate not being used in MVC4 for DataType.Date

I'm upgrading an MVC3 application to MVC4 using the instructions from Microsoft. Everything went fairly smoothly - except a few of my date model properties are now rendering differently. For example, one of these properties is defined in the viewmodel like this:
[Required(ErrorMessage = "Required")]
[DataType(DataType.Date)]
[RegularExpression(#"([1-9]|0[1-9]|1[012])...",
ErrorMessage = "Format is mm/dd/yyyy")]
[FormatHint("mm/dd/yyyy")]
[InputSize("small")]
public string Date { get; set; }
Before upgrading to MVC4, this would be rendered via calling #Html.EditorFor(m => m.Date) which would use a custom EditorTemplate - the String.cshtml template (since it's a string!). I have some custom data annotations that formats the html so it utilizes a field layout, jQueryUI, and twitter Bootstrap on the client side. The validation is done via jquery validation unobtrusive. Anyhow, this is how it previously rendered:
Now that I'm using MVC4, the String.cshtml editor template is not being called for this property any longer. It renders like this (in Chrome using the HTML5 editor stuff, I assume):
The input element looks pretty much the same - all the jQuery validation bits are in there - the only difference seems to be the type attribute is now type="date", where before it was type="text".
I'd like to continue using the String.cshtml EditorTemplate for this datatype. I'm thinking there might be a data annotation that I can put on the ViewModel property to provide a TemplateHint for #Html.EditorFor(...). If not this, I'd like to know the custom EditorTemplate that I can write to hijack MVC4's formatting (I've already tried DateTime.cshtml - it's not being called either). If not either of those, then I'm open to suggestions on how to get my property rendering like it used to.
In MVC4, the used template is determinated from :
The UIHintAttribute if any
The DataTypeAttribute if any
The type of the property
In MVC3, the DataTypeAttribute was not used.
Your property has a
[DataType(DataType.Date)]
so the template Date.cshtml is used. If it does not exists, default rendering is executed.
You have two options to resolve your problem :
Add a UIHint("String") on your property, or
Rename your editor template from String.cshtml to Date.cshtml

Posting multiple values using MVC

I have a model with a property that points to a file that contains HTML. My strongly typed view to this model uses a custom HTML helper method to resolve and return the HTML from the file. Works great so far.
The HTML read from each file will contain various controls whose values I need to retrieve when the form is POSTed.
What would be the best way to have access to the POSTed control values in my controller method?
I would prefer a non jQuery solution, but I am not sure if the MVC framework can provide these values to me? Can it provide a list of key/value pairs to the controller somehow?
You could use the FormCollection in ASP.NET MVC.
public ActionResult SomeAction(FormCollection form) {
...
}
You have essentially two options.
1) Use the old fashioned Request variables as all we have done in ASP.NET web forms.
For example in your controller action method you can retrieve any value present on the form with the following method
public ActionResult SomeAction() {
var request = this.ControllerContext.HttpContext.Request;
bool boolParam = bool.Parse( request["boolParam"] ?? "false" );
}
2) Create a custom Model Binder to let the framework pack those values in a custom class object.
This method would be a little bit more difficult at the beginning because you have to create a custom Model Binder but it favour readability on your controller code. For further details on creating custom model binders give a look at the following links (you can find more with a simple search)
Custom Model Binder for Complex composite objects
Custom Model Binder and More UI Validation in ASP.NET MVC
A Custom ASP.NET MVC Model Binder for Repositories
Hope it helps
Is the content of the HTML files dynamic or known at design time? If you know it now, you could have each one post to it's own action and then strongly type the parameters.

Resources