外部キー関係を結合ASP.NET MVCモデル
-
20-08-2019 - |
質問
これは、フォームの入力に私のモデルに外部キー関係をバインドすることは可能ですか?
は、私がCar
とManufacturer
の間に1対多の関係を持っていると言います。私はint
設定するための選択入力を含むIModelBinder
を更新するためのフォームを持っていると思います。私は内蔵のモデルバインディングを使用して、これを行うことができるように期待していたが、私はそれを自分で行う必要があるでしょう考え始めています。
私のアクションメソッドのシグネチャは次のようになります:
public JsonResult Save(int id, [Bind(Include="Name, Description, Manufacturer")]Car car)
フォームポスト値メーカータイプの主キーController
ある名前、説明およびメーカー。名前と説明が正しくセットされますが、モデルバインダーので、理にかなっていないメーカーは、PKフィールドが何であるかわかりません。それは、私は、それが認識してこれの<=>カスタムを記述しなければならないということですか?私は、データアクセスリポジトリは、各<=>コンストラクタのIoCコンテナによってロードされているので、それがうまくいくかどうかはわかりません。
解決
ここに私のテイクだ - これはGetPropertyValueに尋ねられたとき、プロパティは私のモデル組み立てからオブジェクトであり、そして私のNInjectのiKernelに登録IRepository <>を持っているかどうかを確認するために見えることのカスタムモデルバインダーです。それはNinjectから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を置き換えることができます。
他のヒント
確かにそれぞれの車には、一つだけのメーカーがあります。その場合は、あなたは、あなたがセレクトの値をバインドすることができManufacturerIDフィールドを有するべきです。つまり、テキストや値などのidだとメーカー名を持つ必要があり、あなたの選択です。あなたの保存値では、というよりも、メーカーManufacturerIDをバインドします。
<%= Html.DropDownList( "ManufacturerID",
(IEnumerable<SelectListItem>)ViewData["Manufacturers"] ) %>
タグ付き
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)
多分それは遅一つだが、あなたはこれを達成するために、カスタムモデルバインダーを使用することができます。通常、私はそれを@tvanofossonと同じようにしてくださいが、私はAspNetMembershipProviderテーブルにUserDetailsを追加した場合がありました。また、私は唯一のユーザを登録/追加するモデルを作成したので、それはビジネスの観点から正当化できなかったので、私はIDを使用したくありませんでした(とEntityFrameworkから、それをマップ)のみPOCOを使用しているため。このモデルは、同様に、ユーザーとロールのプロパティのすべてのプロパティを持っていました。私はそれの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))%>
私はこれがあなたの役に立てば幸います。