DefaultModelBinder返回子类MVC4

时间:2013-07-17 20:05:03

标签: asp.net-mvc-4 binding subclass defaultmodelbinder

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

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

  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
}

我的控制器代码使用自定义的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)}

    };
}

这样我就可以将我的视图传递给资源(作为DVD,Book或任何其他类型)

@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的原因。现在它正确地绑定/回发了铸造类型,但它没有绑定资源的基类属性,如Title,ResourceID,ResourceTypeID ..

任何人都可以帮助我理解我所缺少的内容,以便它实际绑定基本资源类属性以及派生类型属性。?

1 个答案:

答案 0 :(得分:0)

所以我所要做的就是在自定义的ModelBinder类中覆盖BindProperty方法。

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
    }

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