MVC3 ModelBinder for DynamicObject

2020-04-01 02:52发布

问题:

I'm looking to see if there is a sample project, tutorial, contrib branch or anything like that that details implementing a custom ModelBinder for MVC3 to support objects inheriting from DynamicObject.

I have an domain object that has a dynamic number of properties as defined by the database, and these can change at run time. To make using the object easier I've made my class implementation inherit from DynamicObject and am passing the model to the view via the [dynamic] keyword.

All of the dynamic properties for the object are in a collection property on the object called "Attributes". I'd like to create a series of editor templates to flesh out the model, ideally so all I have to do is make a call along the lines of Html.EditorForModel() and it will dynamically build the UI.

The problem is I'm not having much luck finding an implementation of a ModelBinder capable of inspecting a DynamicObject and scaffolding out the UI (I think that's the right term for this?).

I found the IDictionary ModelBinder project done in one of the MVCConf videos done by Roberto Hernandez (@hernandezrobert) on MVC3 Extensability (source at http://mvcextensibility.codeplex.com/) but I haven't had much luck adapting it to my purposes. I was wondering if anyone else has tried to create a model binder capable of doing what I'm describing? or could maybe point me in the right direction?

回答1:

ModelBinders don't help generate the view, they help map the raw parameters from various web sources (form, querystring, etc) to the input parameters expected by your action method (specifically, if your input param(s) are a class of some sort rather than primitives).

What you're looking for is an example of how to generate the view templates, which I've not seen for dynamics. The best resource for regular view template generation so far that I've seen is Brad Wilson's blog. If you have a means (which it sounds like you do) of figuring out what properties the object has to display along w/ metadata about how to display them (e.g. textarea vs. input type=text, etc), then you should be able to just follow along w/ Brad.



回答2:

I was able to solve my situation by setting up a normal class to serve as the Model for my DanamicObject, and having my properties be stored like so

IList<DynamicProperty> DynamicProperties { get; set; }

I created a custom view for the DynamicObject, and in that view I called one of the helpers to display the DynamicProperties property. This lets MVC iterate through the collection and render each property. Next I have a view for the DynamicProperty that I use to render the property as needed. The key is you also have to render a hidden field containing the primary key of that specific attribute. The ModelBinder in MVC3 does a much better job here than it did in MVc2, so it will render the array index as part of the field's name so that the primary key and value of each property get paired up correctly on the submit. You'll likely want to create a ViewModel specifically for the submitted data, I had issues when I tried to use the same model class used in the details/edit views because I only rendered a subset of fields so they were missing when I bound to the same Model class on postback.

You can handle the save however you would normally but there are a few considerations securitywise for this type of object. Because the number of attributes is dynamic, there is no way to ensure that the same number of fields are submitted as were originally rendered. The user could inject their own, or worse add in fields for a property you may have explicitly excluded. The AntiForgeryToken would prevent these sort of submissions from happened outside of your domain but with the ease and popularity of DOM manipulation provided by libraries like jQuery, cross site postbacks isn't the only concerns and I don't know if the AntiForgeryToken will account for this but I doubt it.

This approach proved to be easier than trying to inherit from DynamicObject, implement GetDynamicMemberNames, and making a custom ModelBinder to use wrap around it.

I did however create a custom ModelMetaData and Validation Provider to handle those aspects, since the properties are not strongly typed, so MVC didn't have any annotations to use.