5

I would like to be able to post any serialized object to an action method and instantiate a new object of the posted type in order to use TryUpdateModel. They didn't teach me any of this stuff in the QBasic help file... How can I instantiate the unknown type based on the posted data?

If it would help, I could theoretically include the name of the type as a string in the posted data. I was hoping to avoid that because it seemed like I would need the full name of the type.

public void Save(object/dynamic whatever, string typename) {
    //Instantiate posted type
    //TryUpdateModel
    context.Entry(Thing).State = EntityState.Modified;
    context.SaveChanges();
}

Here is an example of a serialized object

Thing.Id=1&Thing.Name=blah&Thing.OptionID=1&Thing.ListItems.index=1&Thing.ListItems%5B1%5D.Id=1&Thing.ListItems%5B1%5D.Name=whatever&Thing.ListItems%5B1%5D.OptionID=2&Thing.ListItems%5B1%5D.ThingID=1&Thing.ListItems%5B1%5D.EntityState=16

From Fiddler

Thing.Id                            1
Thing.Name                          blah
Thing.OptionID                      1
Thing.ListItems.index               1
Thing.ListItems[1].Id               1
Thing.ListItems[1].Name             whatever
Thing.ListItems[1].OptionID         2
Thing.ListItems[1].ThingID          1
Thing.ListItems[1].EntityState      16
Benjamin
  • 3,134
  • 6
  • 36
  • 57
  • Use a custom model binder for the parameter in question. Then the parameter could be of any type, such as `IGorilla.Bas`. – bzlm Jul 28 '11 at 16:45
  • @bzlm I was wondering if I would need to do that. I have examples of custom model binders in MVC books and online. It is a little over my head at this point, or my coffee level maybe. The examples seem to have a different focus. Can you point me in the right direction as far as how to make the binder identify the unknown type and notify the controller of the type? Do I need to use reflection? Thank you. – Benjamin Jul 28 '11 at 18:24
  • if you explicitly say that a certain parameter of a certain action method uses a certain model binder (ie. `MyActionMethod([ModelBinder(typeof(MyModelBinder))]MyModel myModel)`), then there is no guessing, identification or reflection involved. It will simply rely upon `MyModelBinder` to turn `myModel` (as it is passed over HTTP) into a `MyModel`. This is explicit and transparent, and can be useful when you don't want to hide the fact that complex binding takes place. You can register model binders for types as well, to avoid needing that attribute on a parameter on every action method. – bzlm Jul 28 '11 at 19:07
  • @bzlm Thanks for your reply. But I don't think I understand. Does `MyModel` represent some kind of dynamic type? How will the action method know what the type is at runtime? And what would `MyModelBinder` need to do differently than the default? – Benjamin Jul 28 '11 at 19:25
  • the only dynamic type is `dynamic`. But that's not relevant here, since the action method specifies what the desired end-result type is *after* all model binding has taken place. The example in your question can be accomplished using the complex model binding functionality offered by the default model binder, but if the resulting type isn't known at compile-time, then how will the action method know what to do with the object passed to the method? What is your actual scenario here? Surely you're not hoping to over-generalize things? – bzlm Jul 28 '11 at 20:56
  • The action method's parameter could be a string of the name of the object's type. I want to instantiate the type (or maybe a proxy version? I don't totally get it yet) using the string and then fill it with the values from the model binder. I thought `TryUpdateModel` could help with this. – Benjamin Jul 28 '11 at 21:20
  • @bzlm I am able to instantiate an EF Proxy of the unknown type by passing the type name as a string - but when I call `tryupdatemodel` nothing happens, returns true, no `viewdata` errors, etc. I think I will post a new question regarding (try)updatemodel since I technically have the answer to my posted question here. – Benjamin Jul 29 '11 at 00:56
  • yes, do that; [@Darin's answer](http://stackoverflow.com/questions/6861683/how-can-i-make-a-controller-action-take-a-dynamic-parameter/6869262#6869262) is more or less what I'm suggesting anyway. :) – bzlm Jul 29 '11 at 09:39
  • @bzlm thanks for your help bzlm. I am definitely hoping to over-generalize things, but I don't plan on doing it more than once :) – Benjamin Jul 29 '11 at 19:34

1 Answers1

10

You could write a custom model binder which uses reflection and the typeName parameter:

public class MyModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue("typename");
        if (typeValue == null)
        {
            throw new Exception("Impossible to instantiate a model. The \"typeName\" query string parameter was not provided.");
        }
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)),
            true
        );
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }
}

and then simply:

[HttpPost]
public ActionResult Save([ModelBinder(typeof(MyModelBinder))] object model) 
{
    context.Entry(model).State = EntityState.Modified;
    context.SaveChanges();
    return View();
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928