7

是否可以将我的模型上的外键关系绑定到表单输入?

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>();
    }

}

您显然可以用 Ninject 代替您的 DI 容器和您自己的存储库类型。

于 2010-06-04T02:18:42.483 回答
3

当然,每辆车只有一个制造商。如果是这种情况,那么您应该有一个 ManufacturerID 字段,您可以将 select 的值绑定到该字段。也就是说,您的选择应该将制造商名称作为文本,将 id 作为值。在您的保存值中,绑定制造商 ID 而不是制造商。

<%= 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)
于 2009-03-19T15:43:18.533 回答
2

也许这是迟到的,但您可以使用自定义模型绑定器来实现这一点。通常我会以与@tvanofosson 相同的方式执行此操作,但我有一个案例,我将 UserDetails 添加到 AspNetMembershipProvider 表中。由于我也只使用 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 回答