2013-07-17 21 views
1

我一直在尝试阅读关于这个DefaultModelBinder几天,但我仍然很困惑。我正在使用MVC 4 & EF 5 TablePerHiearchy结构。DefaultModelBinder返回子类MVC4

我的问题是,我有一个基类资源:

public class Resource : PocoBaseModel 
    { 
    private int _resourceID; 
    private string _title; 
    private string _description; 

    //public accessors 
    } 

有子类(DVD,电子书,书等)

public class DVD : Resource 
{ 
    private string _actors; 
    //more fields and public accessors 
} 

我的控制器代码使用自定义的模型绑定器

[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)} 

    }; 
} 

,这样我可以通过我的看法资源(铸造的DVD,书籍,或任何其他类型)

@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> 
} 

并将其绑定是根据它的派生属性恰好在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; 

我的第一个问题是,当我张贴的形式回来,这又回到作为一种资源,而不是作为铸造型(所以我失去了所有的派生型属性)这就是为什么我创建的ResourceModelBinder。现在它正确绑定/回发铸造类型,但它不绑定资源的基类属性,如标题,资源ID,资源类型ID ..

任何人都可以帮助我了解我缺少的东西,以便它实际上绑定基础资源类属性以及派生类型属性。

回答

0

所以我所要做的就是重写BindProperty方法以及我自定义的ModelBinder类。

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 to a 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 
    } 

此方法遍历每个可以分配的属性。它会得到属性的类型,以便您可以将其值从表单转换为适当的类型。在可空类型和字符串属性列表中存在一些挑战,但它成功绑定,所以我希望这可能对某人有用。