asp.net mvc 4 - DefaultModelBinder Returning Subclass MVC4 -
i've been trying read defaultmodelbinder couple of days i'm still confused. using mvc 4 & ef 5 tableperhiearchy structure.
my problem have base class of resource:
public class resource : pocobasemodel { private int _resourceid; private string _title; private string _description; //public accessors }
that has sub classes (dvd, ebook, book, etc)
public class dvd : resource { private string _actors; //more fields , public accessors }
my controller code uses custom modelbinder
[httppost] public actionresult create([modelbinder(typeof(resourcemodelbinder))] resource resource) { //controller code } public class resourcemodelbinder : defaultmodelbinder { public override object bindmodel(controllercontext controllercontext, modelbindingcontext bindingcontext) { var type = controllercontext.httpcontext.request.form["discriminatorvalue"]; bindingcontext.modelname = type; bindingcontext.modelmetadata = modelmetadataproviders.current.getmetadatafortype(null, resourcetypemap[type]); return base.bindmodel(controllercontext, bindingcontext); } static dictionary<string, type> resourcetypemap = new dictionary<string, type> { {"resource", typeof(resource)}, {"book", typeof(book)}, {"dvd", typeof(dvd)}, {"ebook", typeof(ebook)}, {"hardware", typeof(hardware)}, {"software", typeof(software)} }; }
so pass view resource (casted dvd, book, or other type)
@model models.resource @{ viewbag.title = "create"; } <h2>create</h2> @using (html.beginform("create", "admin", null, formmethod.post, null)) { @html.validationsummary(true) <fieldset> <legend>resource</legend> @html.hiddenfor(model => model.resourceid) @html.hiddenfor(model => model.resourcetypeid) @html.hiddenfor(model => model.committed) @html.partial("_createoredit", model) <p> <input type="submit" value="create"/> </p> </fieldset> }
and bind based on derived properties happens in switch inside partialview.
@using models.viewmodels; @using models.resourcetypes; @using helper; @model models.resource @html.hiddenfor(model => model.discriminatorvalue); <table cellspacing="2" cellpadding="2" border="0"> @{ string type = model.discriminatorvalue; switch (type) { case "book": book book = (book)model; <tr> <td colspan="2"> <div class="editor-label" style="padding-top: 15px;"> @html.labelfor(model => model.title) </div> <div class="editor-field"> @html.textareafor(model => model.title, new { style = "width: 750px; height: 65px;" }) @html.validationmessagefor(model => model.title) </div> </td> </tr> <tr> <td> <div class="editor-label"> @html.labelfor(model => book.edition) </div> <div class="editor-field"> @html.textboxfor(model => book.edition, new { style = "width: 150px;" }) @html.validationmessagefor(model => book.edition) </div> </td> <td> <div class="editor-label"> @html.labelfor(model => book.author) </div> <div class="editor-field"> @html.editorfor(model => book.author) @html.validationmessagefor(model => book.author) </div> </td> </tr> <tr> <td> <div class="editor-label"> @html.labelfor(model => book.pages) </div> <div class="editor-field"> @html.textboxfor(model => book.pages, new { style = "width: 75px;" }) @html.validationmessagefor(model => book.pages) </div> </td> </tr> <tr> <td colspan="2"> <div class="editor-label"> @html.labelfor(model => model.description) </div> <div class="editor-field"> @html.textareafor(model => model.description, new { style = "width: 750px; height: 105px;" }) @html.validationmessagefor(model => model.description) </div> </td> </tr> <tr> <td colspan="2"> <div class="editor-label"> @html.labelfor(model => model.adminnote) </div> <div class="editor-field"> @html.textareafor(model => model.adminnote, new { style = "width: 750px; height: 105px;" }) @html.validationmessagefor(model => model.adminnote) </div> </td> </tr> <tr> <td> <div class="editor-label"> @{ int copies = model == null ? 1 : model.copies; } @html.labelfor(model => model.copies) </div> <div class="editor-field"> @html.textboxfor(model => model.copies, new { style = "width: 75px;", @value = copies.tostring() }) @html.validationmessagefor(model => model.copies) </div> </td> </tr> <tr> <td> <div class="editor-label"> @html.labelfor(model => book.isbn10) </div> <div class="editor-field"> @html.editorfor(model => book.isbn10) @html.validationmessagefor(model => book.isbn10) </div> </td> <td> <div class="editor-label"> @html.labelfor(model => book.isbn13) </div> <div class="editor-field"> @html.editorfor(model => book.isbn13) @html.validationmessagefor(model => book.isbn13) </div> </td> </tr> break;
my first problem when posted form back, went resource , not casted type (so losing derived type properties) why created resourcemodelbinder. correctly binds/postsback casted type not bind base class properties of resource title, resourceid, resourcetypeid..
can me understand missing binds base resource class properties derived type properties.?
so had override bindproperty method in custom modelbinder class.
protected override void bindproperty(controllercontext controllercontext, modelbindingcontext bindingcontext, system.componentmodel.propertydescriptor propertydescriptor) { if (propertydescriptor.displayname != null) { var form = controllercontext.httpcontext.request.form; string currentpropertyformvalue = string.empty; string formderivedtypekey = bindingcontext.modelname.tolower() + "." + propertydescriptor.displayname; string formbasetypekey = propertydescriptor.displayname; list<string> keywordlist = null; type conversiontype = propertydescriptor.propertytype; if (!string.isnullorempty(form[formderivedtypekey]) || !string.isnullorempty(form[formbasetypekey])) { if (!string.isnullorempty(form[formderivedtypekey])) { //store current derived type property currentpropertyformvalue = form[formderivedtypekey]; } if (!string.isnullorempty(form[formbasetypekey])) { //store current base type property currentpropertyformvalue = form[formbasetypekey]; } } if (conversiontype.isgenerictype) { if (conversiontype.getgenerictypedefinition() == typeof(list<>)) { if (propertydescriptor.displayname == "keywords") { string[] keywords = currentpropertyformvalue.split(','); if (keywords != null && keywords.count() > 0) { //create keyword list keywordlist = new list<string>(); foreach (var item in keywords) { if (!string.isnullorempty(item) && !item.contains(',')) { keywordlist.add(item); } } } } } if (conversiontype.getgenerictypedefinition() == typeof(nullable<>)) { // nullable type property.. re-store nullable type safe type conversiontype = nullable.getunderlyingtype(conversiontype) ?? propertydescriptor.propertytype; } } if (!string.isnullorempty(currentpropertyformvalue)) { //bind property if (propertydescriptor.displayname != "keywords") { propertydescriptor.setvalue(bindingcontext.model, convert.changetype(currentpropertyformvalue, conversiontype)); } else propertydescriptor.setvalue(bindingcontext.model, convert.changetype(keywordlist, conversiontype)); } } else base.bindproperty(controllercontext, bindingcontext, propertydescriptor); //default condition }
this method goes through every property assigned. gets type of property can convert value form appropriate type. challenges there nullable types , list of string property binds hope may useful someone.
Comments
Post a Comment