2009-03-19 13 views
7

モデルの外部キー関係をフォーム入力にバインドできますか?ASP.NET MVCモデルの外部キー関係のバインド

CarManufacturerの間に1対多の関係があります。私はManufacturerのための選択入力を含むCarを更新するためのフォームを持っています。組み込みのモデルバインディングを使ってこれを実行できることを期待していましたが、私は自分でそれをやらなければならないと思っています。

public JsonResult Save(int id, [Bind(Include="Name, Description, Manufacturer")]Car car) 

フォームポスト値の名前、説明およびメーカー、メーカーはタイプintの主キーです:

私のアクションメソッドのシグネチャは次のようになります。名前と説明は適切に設定されますが、ModelバインダーはPKフィールドが何であるかわからないので、メーカーにとっては意味がありません。それは私がこれを知っているカスタムIModelBinderを書かなければならないということですか?データアクセスリポジトリが各コンストラクタのIoCコンテナを介してロードされてから、どのように動作するのか分かりません。

答えて

6

GetPropertyValueを要求されたときに、そのプロパティがモデルアセンブリのオブジェクトであるかどうかを調べ、IRepository <>が自分のNInject IKernelに登録されているカスタムモデルバインダーです。 NinjectからIRepositoryを取得できる場合、IRepositoryを使用してそれを使用して外部キーオブジェクトを取得します。

public class ForeignKeyModelBinder : System.Web.Mvc.DefaultModelBinder 
{ 
    private IKernel serviceLocator; 

    public ForeignKeyModelBinder(IKernel serviceLocator) 
    { 
     Check.Require(serviceLocator, "IKernel is required"); 
     this.serviceLocator = serviceLocator; 
    } 

    /// <summary> 
    /// if the property type being asked for has a IRepository registered in the service locator, 
    /// use that to retrieve the instance. if not, use the default behavior. 
    /// </summary> 
    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, 
     PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) 
    { 
     var submittedValue = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 
     if (submittedValue == null) 
     { 
      string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, "Id"); 
      submittedValue = bindingContext.ValueProvider.GetValue(fullPropertyKey); 
     } 

     if (submittedValue != null) 
     { 
      var value = TryGetFromRepository(submittedValue.AttemptedValue, propertyDescriptor.PropertyType); 

      if (value != null) 
       return value; 
     } 

     return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); 
    } 

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType) 
    { 
     string fullPropertyKey = CreateSubPropertyName(bindingContext.ModelName, "Id"); 
     var submittedValue = bindingContext.ValueProvider.GetValue(fullPropertyKey); 
     if (submittedValue != null) 
     { 
      var value = TryGetFromRepository(submittedValue.AttemptedValue, modelType); 

      if (value != null) 
       return value; 
     } 

     return base.CreateModel(controllerContext, bindingContext, modelType); 
    } 

    private object TryGetFromRepository(string key, Type propertyType) 
    { 
     if (CheckRepository(propertyType) && !string.IsNullOrEmpty(key)) 
     { 
      Type genericRepositoryType = typeof(IRepository<>); 
      Type specificRepositoryType = genericRepositoryType.MakeGenericType(propertyType); 

      var repository = serviceLocator.TryGet(specificRepositoryType); 
      int id = 0; 
#if DEBUG 
      Check.Require(repository, "{0} is not available for use in binding".FormatWith(specificRepositoryType.FullName)); 
#endif 
      if (repository != null && Int32.TryParse(key, out id)) 
      { 
       return repository.InvokeMethod("GetById", id); 
      } 
     } 

     return null; 
    } 

    /// <summary> 
    /// perform simple check to see if we should even bother looking for a repository 
    /// </summary> 
    private bool CheckRepository(Type propertyType) 
    { 
     return propertyType.HasInterface<IModelObject>(); 
    } 

} 

DI容器と自分のリポジトリタイプをNinjectに置き換えることはできます。

+2

非常に参考になる例!しかし、IModelBinderProvider'インターフェースを使用してバインダーの内部をチェックするのではなく、モデルのバインダーをこのモデルバインダーの対象としています。 Brad Wilsonはこれについて[http://bradwilson.typepad.com/blog/2010/10/service-location-pt9-model-binders.html]について書きました。 –

+0

はい、それは素晴らしいでしょう。私はまだMVC3に更新していない。 –

3

もちろん、各車にはメーカーが1社しかありません。その場合、選択する値をバインドできるManufacturerIDフィールドが必要です。つまり、選択にはメーカー名がテキストとして、IDに値が設定されている必要があります。保存値で、ManufacturerではなくManufacturerIDをバインドします。

ViewData["Manufacturers"] = db.Manufacturers 
           .Select(m => new SelectListItem 
              { 
               Text = m.Name, 
               Value = m.ManufacturerID 
              }) 
           .ToList(); 

そして

public JsonResult Save(int id, 
         [Bind(Include="Name, Description, ManufacturerID")]Car car) 
+1

モデルがPOCOを使用して構築されている場合、 'Car'に' ManufacturerID'プロパティを持つことは私にとっては正しいとは思われません。これは本当にこの種のモデル結合を解決するための好ましい方法ですか? –

+0

あなたは何を意味するのか分かりません。通常、私は車とメーカーのエンティティを関連付けるための外部キーフィールドを持っています。これを「ID」フィールドにするのはかなり標準的です。あなたのモデルにこのフィールドを公開することを選択しないかもしれないと思いますが、確かに可能です。私は通常、LINQtoSQLを使用します。私は、CarエンティティにManufacturerIDプロパティと関連するManufacturerエンティティの両方が存在することを保証します。 – tvanfosson

2

<%= Html.DropDownList("ManufacturerID", 
     (IEnumerable<SelectListItem>)ViewData["Manufacturers"]) %> 

多分それは遅一つだが、あなたはこれを達成するために、カスタムモデルバインダーを使用することができます。通常は@tvanofossonと同じ方法ですが、AspNetMembershipProviderテーブルにUserDetailsを追加する場合がありました。私はPOCOだけを使用し(EntityFrameworkからマップするので)、ビジネスの観点から正当化されていないため、IDを使用したくないので、ユーザーを追加/登録するためのモデルを作成しました。このモデルには、ユーザーとロールプロパティのすべてのプロパティも含まれていました。ロールのテキスト名をRoleModel表現にバインドしたかったのです。それは私がやったことは基本的です:

public class RoleModelBinder : DefaultModelBinder 
{ 
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
    { 
     string roleName = controllerContext.HttpContext.Request["Role"]; 

     var model = new RoleModel 
          { 
           RoleName = roleName 
          }; 

     return model; 
    } 
} 

は、それから私は、Global.asaxのに以下を追加する必要がありました:

ModelBinders.Binders.Add(typeof(RoleModel), new RoleModelBinder()); 

とビューでの使用:

<%= Html.DropDownListFor(model => model.Role, new SelectList(Model.Roles, "RoleName", "RoleName", Model.Role))%> 

私はこのことを願っていますあなたを助けます。

+0

私はこれを質問として再掲載しました。http://stackoverflow.com/questions/3642870/custom-model-binder-for-dropdownlist-not-selecting-correct-value – nfplee

関連する問題