Click here to Skip to main content
Click here to Skip to main content

UpdateModel/TryUpdateModel gotchas with models created through reflection

By , 15 Apr 2014
Rate this:
Please Sign up or sign in to vote.

"THE ARTICLE IS NO LONGER MAINTAINED HERE. PLEASE POST ANY FURTHER DISCUSSIONS/QUESTIONS IN MY BLOG http://www.prideparrot.com/blog/archive/2012/6/gotchas_in_explicit_model_binding" - Author

Explicit model binding

The Model Binding feature takes away most of the burden from developers by taking the responsibility of model instantiation from the information available in the request. Sometimes we meet cases where we need to trigger the model binding process explicitly inside a controller. MVC provides two methods for rescue: UpdateModel and TryUpdateModel.

Both these methods perform the same operation, that is they update the model from the value providers. The difference between them is the UpdateModel throws exception if the model state is not valid while TryUpdateModel returns a boolean as false. Both these methods are generic and we don't need to explicitly specify the generic parameter.

public ActionResult Save(FormCollection form)
{
    var emp = new Employee();

    if(TryUpdateModel(emp, form))
    {
        _empRepo.Save(emp);

        return RedirectToAction("Index");
    }

    return View();
}

Both the methods take overloads that accepts an IValueProvider. When you don't pass a particular value provider the controller uses all the available value providers to fill the instantiated model.

There is a peculiar problem with these two methods when we try to bind a model that is instantiated through reflection. In this article we are going to see about the issue and how we can overcome that.

The problem

Let's take the same example but in this time the Employee class is instantiated through reflection.

public ActionResult Save(FormCollection form)
{
    var empType = Type.GetType("Example.Models.Employee");
    var emp = Activator.CreateInstance(empType);

    if(TryUpdateModel(emp, form))
    {
        _empRepo.Save(emp);

        return RedirectToAction("Index");
    }

    return View();
}

When you run the above code you will find that the properties of emp object will never get filled even though those values are available in the form.

Why this happens?

The way the controller gets the type of the passed model is causing the problem. All the overloaded versions of UpdateModel/TryUpdateModel calls finally a version of TryUpdateModel and that looks as below:

protected internal bool TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, 
string[] excludeProperties, IValueProvider valueProvider) where TModel : class
{
    ...
    IModelBinder binder = Binders.GetBinder(typeof(TModel));

    ModelBindingContext bindingContext = new ModelBindingContext()
    {
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(TModel)),
        ModelName = prefix,
        ModelState = ModelState,
        PropertyFilter = propertyFilter,
        ValueProvider = valueProvider
    };
    binder.BindModel(ControllerContext, bindingContext);
    return ModelState.IsValid;
}

The problem is the that method gets the type of the passed model from the generic parameter instead of from the passed object i.e., typeof(TModel) instead of model.GetType().

It uses that way in couple of places, to get the binder for the type and when instantiating the ModelMetaData.

The problem is when you get the type of the passed object from the generic parameter it returns the type correctly if the object is instantiated directly but if the object is instantiated through reflection then type(TModel) will return the type as System.Object instead of the true type. Since the type System.Object don't have a built-in model binder the values are not getting bound to the model when we use these methods.

How can we fix this?

We can't fix this issue but I hope the MVC team will take care of that in the coming versions. But still that time we can achieve the model binding for entities created through reflection by doing that work manually forgetting the built-in methods.

public ActionResult Save(FormCollection form)
{
    var empType = Type.GetType("Example.Models.Employee");
    var emp = Activator.CreateInstance(empType);

    var binder = Binders.GetBinder(empType);

    var bindingContext = new ModelBindingContext()
    {
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => emp, empType),
        ModelState = ModelState,
        ValueProvider = form
    };      

    binder.BindModel(ControllerContext, bindingContext);

    if (ModelState.IsValid)
    {
       _empRepo.Save(emp);

        return RedirectToAction("Index");
    }

    return View();
}

In the above code we just duplicated the code from the TryUpdateModel ripping off some of the things and most importantly replacing the typeof(TModel) into model.GetType().

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

After2050
Software Developer Trigent Software Private Limited
India India
I'm a software developer from south tip of India. I spent most of the time in learning new technologies. I've a keen interest in client-side technologies especially JavaScript and admire it is the most beautiful language ever seen.
 
I like sharing my knowledge and written some non-popular articles. I believe in quality and standards but blames myself for lagging them.
 
I believe in small things and they makes me happy!
Follow on   Twitter

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140421.2 | Last Updated 15 Apr 2014
Article Copyright 2012 by After2050
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid