I have a complex object that I'm binding off of a form. The model binder looks like this:
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var form = new MyForm();
var myObject = ...; //try to load up the object
/* logic to populate values on myObject */
form.MyObject = myObject;
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, new ValueProviderResult(form, "", CultureInfo.CurrentUICulture));
return form;
}
and it is doing what it's supposed to; I get a correctly populated MyForm out of it, and a reference to the same MyForm instance is included in the ModelState. However, the form does not get validated using either the DataAnnotations or my CustomValidation validation. In order to cause that validation, I have to add a TryValidateModel() call in my Controller:
[HttpPost]
public ActionResult ProcessMyForm(MyForm form)
{
//ModelState has the MyForm instance inside of it
//TryValidateModel(ModelState); //this does not work
TryValidateModel(form); //this works
if (!ModelState.IsValid)
{
return View("Complete", form);
}
return RedirectToAction("Index");
}
Which not only calls into my custom validation, but also updates the value of ModelState.IsValid.
In addition to my title question, this raises a couple of questions:
Why does TryValidateModel(ModelState) not validate the form when ModelState has a reference to the same instance of the form that TryValidateModel(form) correctly validates?
Why does TryValidateModel(form) cause the value of ModelState.IsValid to be updated?
In general, why are the binders responsible for updating ModelState?
The ModelBinder's responsibility is to bind values from the request into the model(s) you are using.
The ModelState property is just a dictionary containing the current state of you models. Look at modelstate like an errorlist.
When you have a custom ModelBinder you map the values from the request into the class of your choice. That will end up as a parameter into your actionmethod.
I wouldn't agree with you that modelbinders are responsible for updating the ModelState since the ModelBinder is run when it binds the values, it can still have IsValid=true before you run TryValidateModel.
When you later run the TryValidateModel (or ValidateModel for that matter) it will update the ModelState property with whatever errors you have. You can also use different types to validation methods (DataAnnotations, IValidatableObject...)
Related
I have an action that looks like
public ActionResult GetUsers(List<int> userIds) {//do stuff}
The list of userIds can become quite long, so I want to use Json.Net to deserialize it. To do so I created an IModelBinder implementation, which works fine for other objects, but never gets called for a List. The IModelBind looks like this
public class JsonBinder : System.Web.Mvc.IModelBinder
{
public object BindModel(System.Web.Mvc.ControllerContext controllerContext, System.Web.Mvc.ModelBindingContext bindingContext)
{ //Do model binding stuff using Json.Net }
}
And I register this model binder with this line
ModelBinders.Binders.Add(typeof(List<int>), new JsonBinder());
However the JsonBinder is never called. Why is this? Should I be using a ValueProvider?
Add the following event in global.asax (or add the code to your existing Application_BeginRequest handler):
protected void Application_BeginRequest()
{
foreach (var type in ModelBinders.Binders.Keys)
{
System.Diagnostics.Trace.WriteLine(
String.Format("Binder for '{0}': '{1}'",
type.ToString(),
ModelBinders.Binders[type].ToString()));
}
}
Then you can check in the VS output window what binders are currently registered. You could see something like this:
Binder for 'System.Web.HttpPostedFileBase': 'System.Web.Mvc.HttpPostedFileBaseModelBinder'
Binder for 'System.Byte[]': 'System.Web.Mvc.ByteArrayModelBinder'
Binder for 'System.Data.Linq.Binary': 'System.Web.Mvc.LinqBinaryModelBinder'
Binder for 'System.Threading.CancellationToken': 'System.Web.Mvc.CancellationTokenModelBinder'
You could also check if there is any ModelBinderProvider that could be selecting the binder provider, because the order for selecting which model binder is used is as follows:
The attribute on the parameter of the action. See GetParameterValue method of the ControllerActionInvoker class
The Binder returned from the IModelBinderProvider. See GetBinder method in the ModelBinderDictionary class
The Binder globally registered in the ModelBinders.Binders dictionary.
The Binder defined in the [ModelBinder()] attribute for the model type.
The DefaultModelBinder.
Use a similar approach to check the model binder providers in the BeginRequest event:
foreach (var binderprovider in ModelBinderProviders.BinderProviders)
{
System.Diagnostics.Trace.WriteLine(String.Format("Binder for '{0}'", binderprovider.ToString()));
}
Additionally, you could try to add Glimpse via nuget, as one of the tabs provides information about the model binder used for each of the parameters in the controller action.
Hopefully this will help you tracing why your model binder is not being used.
Have you tried using ModelBinder attribute in the action method?
public ActionResult GetUsers([ModelBinder(typeof(JsonBinder))] List<int> userIds)
Ref: https://msdn.microsoft.com/en-us/library/system.web.mvc.modelbinderattribute(v=vs.118).aspx
What happens during a controller method call? Does MVC for every PUBLIC method in your controller evaluate/set ModelState? Does it test EVERY class in the method parameters??
public ActionResult Create(Entity myEntity, AnotherEntity, myEntity2)
{
if (ModelState.IsValid)
{
If I had a return of int versus ActionResult:
public int Create(Entity myEntity, AnotherEntity, myEntity2)
{
if (ModelState.IsValid)
{
Would there still be a ModelState with the evaluated classes??
Actually it's not the controller. It's the model binder. The responsibility of the model binder is to instantiate the corresponding model given the request values. So the first step is model binding and the second step is validation. The first step is done by the model binder. If there's an error during this step (for example you have attempted to bind an integer field on your model to an input text in which the user entered some arbitrary text), the model binder automatically adds an error to the model state, so once you enter the controller action you could test whether the ModelState.IsValid.
If model binding succeeds then you have an instance of the model that is now passed to the corresponding validation framework. So for example if you are using Data Annotations and decorated the model properties with validation attributes, they will be evaluated and once again if there are errors they will automatically be added to the ModelState.
If I had a return of int versus ActionResult:
You would violate standard conventions in ASP.NET MVC where all controller actions must return ActionResult. But the return type really has nothing to do with model binding of input parameters and validation. The return type could be any of the possible derived classes of ActionResult or a custom one.
So for example if you want to render the HTML representation of your model you return a ViewResult. If you want to return the JSON representation of your model you return a JsonResult. If you want to return some static string you return a ContentResult. If you want to allow the user download a file you return a FileResult. And so on.
EDIT - We're using MVC4 Dev Preview....
I'm implementing an edit page for a FishingTrip class. FishingTrip contains a child collection of simple Crew objects (i.e. FishingTripID, CrewID, CrewPosition).
I'm using Jarrett Meyer's approach to add, edit and delete from the Crew collection.
I'm using unobtrusive validation to specify that the properties of Crew are all Required.
My problem: when I logically-delete an item from the list (as per Jarrett's method), I don't want that item to be validated.
I have successfully tweaked the "removeRow" method on the client-side to disable unobtrusive validation for the logically-deleted item, so that the form will post despite there being an item that contains blank fields.
In my controller method [HttpPost] Edit, ModelState.IsValid starts off as false (as expected - because of the logically-deleted item that contains blank fields.) So I remove that item from my ViewModel.... but ModelState.IsValid is still false.
In summary, I (think I) want to modify my ViewModel within the controller method to remove the offending item, then call some kind of "revalidate", and have ModelState.IsValid show up as true.
Any ideas?
Once you have removed the offending item(s), clear the ModelState and validate again, like so:
ModelState.Clear();
TryValidateModel(crew); // assumes the model being passed is named "crew"
Note: Be carefull when use TryValidateModel method because this method does not validate nested object of model (As mentioned by #Merenzo).
Late to the game, but still:
I was also looking for a way to validate model after doing some tweaks to it (more precisely - to the items of its nested collection) - and TryValidateModel didn't work for me, as it doesn't process nested objects.
Finally, I settled with custom model binder:
public class MyItemModelBinder : DefaultModelBinder
{
protected override void OnModelUpdated(
ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
if (bindingContext.ModelType == typeof(MyItemModel))
{
MyItemModel item = (MyItemModel)bindingContext.Model;
//do required tweaks on model here
//(I needed to load some additional data from DB)
}
//validation code will be called here, in OnModelUpdated implementation
base.OnModelUpdated(controllerContext, bindingContext);
}
}
on the model class:
[ModelBinder(typeof(MyItemModelBinder))]
public class MyItemModel : IValidatableObject
{
//...
}
Let's say I have a DB table with columns A and B and I've used the Visual Studio designer to create a Linq objects for this table. All fields are marked NOT NULL.
Now I'm trying to edit this record using typical MVC form editing and model binding, but field B doesn't need to be editable, so I don't include it in the form.
When the post handler binds the object it doesn't populate field B, leaving it null. Now when I save the object I get a DB error saying field B can't be NULL.
The code to save looks something like:
m_DataContext.MyObjects.Attach(myObject);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues, myObject);
m_DataContext.SubmitChanges();
How do I get this to work? Do I need to include field B as a hidden field on the form - I don't really want to do this as it may be updated by other users at the same time and I don't want to stomp over it.
I've found the solution to this problem revolves around getting the entity object associated with the data context before applying the changes. There's a couple of ways of doing this which I've described in separate answers below.
Descend into SQL
This approach ditches LINQ in favour of straight SQL:
public override void SaveMyObject(MyObject o)
{
// Submit
m_DataContext.ExecuteCommand("UPDATE MyObjects SET A={0} WHERE ID={1}", o.ID, o.A);
}
I like this approach the best because of it's simplicity. As much as I like LINQ I just can't justify it's messiness with this problem.
Use a Custom Model Binder
This approach uses a custom model binder to create the entity object and associate with the data context, before the binding takes place.
public class MyObjectBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
MyObject a = ((MyObjectController)controllerContext.Controller).Repository.GetMyObjectForUpdate(bindingContext.ValueProvider["ID"].AttemptedValue.ToString());
return a;
}
}
The repository then creates the object and associates it with the data context:
public Object GetMyObjectForUpdate(string id)
{
MyObject o=new MyObject();
o.ID=id;
m_DataContext.Articles.Attach(o);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
return o;
}
The action handler needs to be attributed to use the model binder...
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult EditMyObject([ModelBinder(typeof(MyObjectBinder))] MyObject o)
{
if (!ModelState.IsValid)
return View("EditMyObject", a);
Repository.SaveMyObject(a);
return RedirectToAction("Index");
}
and finally SaveMyObject simply calls datacontext.SubmitChanges().
For this to work I also needed to set the update check attributes on all columns to Never (in the dbml file).
Overall, this approach works but is messy.
Use Two Entity Objects
This approach uses two entity objects, one for the model binding and one LINQ:
public override void SaveMyObject(MyObject o)
{
// Create a second object for use with linq and attach to the data context
MyObject o2 = new MyObject();
o2.ID = o.ID;
m_DataContext.Articles.Attach(o2);
m_DataContext.Refresh(RefreshMode.KeepCurrentValues);
// Apply fields edited by the form
o2.A = o.A;
// Submit
m_DataContext.SubmitChanges();
}
This approeach doesn't require any special handling in the controller (ie: no custom model binding) but still requires
the Update Check property to be set to Never in the dbml file.
You could add a timestamp field and check one on the page with the one in the DB (hiding the timestamp field as well). If a user has updated the record, a concurrency error is returned and the page is refreshed, or left the same iwth the users changes.
I'm working on my first .NET MVC application and using the NerdDinner tutorial as a reference point. One point that is intriguing me at the moment is the UpdateModel() method. (I don't like using things I don't really understand.)
Taken from the NerdDinner tutorial -
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues) {
Dinner dinner = dinnerRepository.GetDinner(id);
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = dinner.DinnerID });
}
My main question is how does the UpdateModel() get access to the formValues passed in the Edit method? Why is the collection not passed in explicitly as a parameter to the method?
UpdateModel() is a Controller helper method that attempts to bind a bunch of different input data sources (HTTP POST data coming from a View, QueryString values, Session variables/Cookies, etc.) to the explicit model object you indicate as a parameter. Essentially, it is only for model binding.
If you express the input parameters for your Action as a strongly-typed model (like a View Model), you've already taken all of the steps that are done behind the scenes when UpdateModel() is called. If you retrieve an object from the DataContext and edit its properties, SaveChanges() is all you need to push the updates back to the database (in this case, Save()).
Example:
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(DinnerViewModel incoming) {
var dinner = dinnerRepository.GetDinner(incoming.DinnerID);
dinner.Description = incoming.Description;
dinnerRepository.Save();
return RedirectToAction("Details", new { id = incoming.DinnerID });
}
However, there is a use-case for using UpdateModel() with a strongly-typed model: when you are passing in a strongly-typed model and want its values to directly replace those of an entity from the database (provided they are all named and typed the same). In this case, you would retrieve the object, use UpdateModel() on it, and its model binding operation will pull in any similarly-named and typed properties from the strongly-typed object to the retrieved object. In other words, it will perform reflection for you.
So, like your example, if you want all properties to update without specifying which to update, and your strongly-typed model and database model have similarly-named properties, you would still want to use UpdateModel() to take advantage of the reflection.
Example:
//
// POST: /Dinners/Edit/2
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(DinnerViewModel incoming) {
var dinner = dinnerRepository.GetDinner(incoming.DinnerID);
UpdateModel(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id = incoming.DinnerID });
}
The only advantage here (over using a FormCollection object) is that you'd have access to all other properties of the strongly-typed object (as shown by incoming.DinnerID).
Conclusion: if you're translating a strongly-typed object to a derived object, it's probably easiest to use UpdateModel(). However, it's largely unnecessary if you are simply updating a few properties of the derived object. Also, be aware that use of the Entity Framework (instead of something like Linq to SQL) makes all of this moot, as it can relate strongly-typed objects and derived objects with its own methods.
It does inspect all the HttpRequest inputs such as Form, QueryString, Cookies and Server variables. I think in this order.
Instead of passing Model object as a parameter to "Post()" action method, we are creating an instance of an Model object within the "Post()" function, and updating it using "UpdateModel()" function. "UpdateModel()" function inspects all the HttpRequest inputs such as posted Form data, QueryString, Cookies and Server variables and populate the employee object.
e.g.
[HttpPost]
[ActionName("Create")]
public ActionResult Create_Post()
{
EmployeeBusinessLayer employeeBusinessLayer =
new EmployeeBusinessLayer();
Employee employee = new Employee();
UpdateModel(employee);
employeeBusinessLayer.AddEmmployee(employee);
return RedirectToAction("Index");
}