7

モデルの外部キー関係をフォーム入力にバインドすることはできますか?

と の間に 1 対多の関係があるCarとしManufacturerます。Car設定用の選択入力を含む更新用のフォームが必要ですManufacturer。組み込みのモデル バインディングを使用してこれを実行できることを望んでいましたが、自分で実行する必要があると考え始めています。

私のアクション メソッドの署名は次のようになります。

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

フォームは Name、Description、Manufacturer の値を投稿します。Manufacturer は type の主キーですint。名前と説明は適切に設定されますが、メーカーではありません。モデル バインダーは PK フィールドが何であるかを認識していないため、これは理にかなっています。それは、これを認識しているという習慣を書かなければならないということIModelBinderですか?私のデータアクセスリポジトリは各Controllerコンストラクターの IoC コンテナーを介してロードされるため、それがどのように機能するかはわかりません。

4

3 に答える 3

6

これが私の見解です。これは、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 に置き換えることもできます。

于 2010-06-04T02:18:42.483 に答える
2

遅いかもしれませんが、カスタム モデル バインダーを使用してこれを実現できます。通常は @tvanofosson と同じ方法で行いますが、AspNetMembershipProvider テーブルに UserDetails を追加するケースがありました。私もPOCOのみを使用する(およびEntityFrameworkからマップする)ので、ビジネスの観点から正当化されなかったため、IDを使用したくなかったため、ユーザーを追加/登録するためだけにモデルを作成しました。このモデルには、ユーザーのすべてのプロパティと Role プロパティもありました。ロールのテキスト名を 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))%>

これがお役に立てば幸いです。

于 2010-04-08T05:37:27.443 に答える