views:

538

answers:

3

I am having a weird issue in ASP.NET MVC with objects not being updated with UpdateModel when passed a formCollection. UpdateModel does not appear to be working properly when the object being updated is created through reflection.

Scenario: I have an application which has approximately 50 lookup tables--each of which includes exactly the same schema including typical fields like id, title, description, isactive, and createdon. Rather than build 50 views, I wanted to have a single view which could display the data from all of the lookup tables. I created an Interface called IReferenceEntity and implemented it in each of the POCOs representing my lookup tables.

Using this interface, I am able to easily populate a view with a record from the lookup table. (I pass the items to the view via the following.)

System.Web.Mvc.ViewPage<MyNamespece.IReferenceEntity>

From the database to the view, every thing works perfectly.

However, when I attempt to update the model on post, I am running into some problems.

If I explicitly declare an object reference like the following, every thing works perfectly and the values of my object are updated with the values from my form. Hence, I can then update the database.

AccountStatus a = new AccountStatus();

UpdateModel(a, formCollection.ToValueProvider());

Unfortunately, hard coding the object type would completely defeat the reason for using an interface.

(A primary objective of the application is to be able to dynamically add new tables such as lookup tables without having to do anything "special". This is accomplished by reflecting on the loaded assemblies and locating any classes which implement a specific interface or base class)

My strategy is to determine the concrete type of the object at postback and then create an instance of the type through reflection. (The mechanism I use to determine type is somewhat primitive. I include it as a hidden field within the form. Better ideas are welcome.)

When I create an instance of the object using reflection through any of the following methods, none of the objects are being updated by UpdateModel.

Type t = {Magically Determined Type}

object b = Activator.CreatorInstance(t);

UpdateModel(b, formCollection.ToValueProvider());


Type t = {Magically Determined Type}

var c = Activator.CreatorInstance(t);

UpdateModel(c, formCollection.ToValueProvider());


Type t = {Magically Determined Type}

IReferenceEntity d = Activator.CreatorInstance(t);

UpdateModel(d, formCollection.ToValueProvider());

Note: I have verified that the objects which are being created through relection are all of the proper type.

Does anyone have any idea why this might be happening? I am somewhat stumped.

If I was really "hard up", I could create factory object which would many instantiate any one of these reference entity/lookup objects. However, this would break the application's ability to allow for new lookup tables to be added and discovered transparently and is just not quite as clean.

Also, I could try deriving from an actual ReferenceEntity base class as opposed to an interface, but I am doubtful whether this would make any difference. The issue appears to be with using reflection created objects in the modelbinder.

Any help is appreciated.

Anthony

A: 

Does your IReferenceEntity contain setters on the properties as well as getters? I would think that the last sample would work if the interface had property setters, though you'd have to cast it to get it to compile.

Type t = {Magically Determined Type}

IReferenceEntity d = Activator.CreatorInstance(t) as IReferenceEntity;

UpdateModel(d, formCollection.ToValueProvider());

Normally the reason that it won't set a property on a class is because it can't find a public setter method available to use via reflection.

tvanfosson
Thank you for your response. Unfortunately, UpdateModel and TryUpdateModel do not support dynamically typed parameters out of the box. Augi's solution overcame the limitation. Thank you for taking the time to help me though!
Anthony Gatlin
A: 

Just a quick "another thing to try":

UpdateModel(d as IReferenceEntity, formCollection.ToValueProvider());

Not sure if that will work, and I haven't tried it myself, but it's the first thing that came to mind.

If I get a chance later I'll peek at the Default Model Binder code and see if there's anything in there that is obvious...

CubanX
It didn't quite work as I mentioned in my answer. However, I really appreciate your taking the time to answer. Thank you!
Anthony Gatlin
A: 

Augi answered this on ASP.NET forums. It worked with only a couple of minor modifications. Thank you Augi.


The problem is that [Try]UpdateModel methods allow to specify model type using generic parameter only so they don't allow dynamic model type specification. I have created issue ticket for this.

You can see TryModelUpdate method implementation here. So it's not difficult to write own overload:

public virtual bool TryUpdateModelDynamic<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties, IDictionary<string, ValueProviderResult> valueProvider) where TModel : class
{
    if (model == null)
    {
        throw new ArgumentNullException("model");
    }
    if (valueProvider == null)
    {
        throw new ArgumentNullException("valueProvider");
    }


    //Predicate<string> propertyFilter = propertyName => BindAttribute.IsPropertyAllowed(propertyName, includeProperties, excludeProperties);  
    IModelBinder binder = Binders.GetBinder( /*typeof(TModel)*/model.GetType());

    ModelBindingContext bindingContext = new ModelBindingContext()
                                             {
                                                 Model = model,
                                                 ModelName = prefix,
                                                 ModelState = ModelState,
                                                 //ModelType = typeof(TModel), // old  
                                                 ModelType = model.GetType(),
                                                 // new  
                                                 //PropertyFilter = propertyFilter,  
                                                 ValueProvider = valueProvider
                                             };
    binder.BindModel(ControllerContext, bindingContext);
    return ModelState.IsValid;
}
Anthony Gatlin