ASP.NET MVC模型绑定外键关系

发布于 2024-07-16 07:13:08 字数 579 浏览 2 评论 0原文

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

假设我在 CarManufacturer 之间存在一对多关系。 我想要一个用于更新 Car 的表单,其中包含用于设置 Manufacturer 的选择输入。 我希望能够使用内置模型绑定来完成此操作,但我开始认为我必须自己做。

我的操作方法签名如下所示:

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

表单发布值“名称”、“说明”和“制造商”,其中“制造商”是 int 类型的主键。 名称和描述设置正确,但制造商设置不正确,这是有道理的,因为模型绑定器不知道 PK 字段是什么。 这是否意味着我必须编写一个自定义的 IModelBinder 来让它意识到这一点? 我不确定这将如何工作,因为我的数据访问存储库是通过每个 Controller 构造函数上的 IoC 容器加载的。

Is it possible to bind a foreign key relationship on my model to a form input?

Say I have a one-to-many relationship between Car and Manufacturer. I want to have a form for updating Car that includes a select input for setting Manufacturer. I was hoping to be able to do this using the built-in model binding, but I'm starting to think I'll have to do it myself.

My action method signature looks like this:

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

The form posts the values Name, Description and Manufacturer, where Manufacturer is a primary key of type int. Name and Description get set properly, but not Manufacturer, which makes sense since the model binder has no idea what the PK field is. Does that mean I would have to write a custom IModelBinder that it aware of this? I'm not sure how that would work since my data access repositories are loaded through an IoC container on each Controller constructor.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

玻璃人 2024-07-23 07:13:08

这是我的看法 - 这是一个自定义模型绑定器,当请求 GetPropertyValue 时,它​​会查看该属性是否是我的模型程序集中的对象,并且具有 IRepository<> 属性。 在我的 NInject IKernel 中注册。 如果它可以从 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 容器和您自己的存储库类型。

Here's my take - this is a custom model binder that when asked to GetPropertyValue, looks to see if the property is an object from my model assembly, and has a IRepository<> registered in my NInject IKernel. If it can get the IRepository from Ninject, it uses that to retrieve the foreign key object.

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

}

you could obviously substitute Ninject for your DI container and your own repository type.

美男兮 2024-07-23 07:13:08

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

<%= 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)

Surely each car only has one manufacturer. If that's the case then you ought to have an ManufacturerID field that you can bind the value of the select to. That is, your select should have the Manufacturer name as it's text and the id as the value. In your save value, bind ManufacturerID rather than Manufacturer.

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

With

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

And

public JsonResult Save(int id,
                       [Bind(Include="Name, Description, ManufacturerID")]Car car)
半边脸i 2024-07-23 07:13:08

也许已经晚了,但您可以使用自定义模型绑定器来实现这一点。 通常我会以与@tvanofosson相同的方式执行此操作,但我遇到了将 UserDetails 添加到 AspNetMembershipProvider 表的情况。 由于我也只使用 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))%>

我希望这对您有帮助。

Maybe it's a late one but you can use a custom model binder to achieve this. Normally I'd do it the same way as @tvanofosson but I had a case where I was adding UserDetails to the AspNetMembershipProvider tables. Since I also use only POCO (and map it from EntityFramework) I didn't want to use an id because it wasn't justified from the business point of view so I created a model only to add/register users. This model had all properties for the user and a Role property as well. I wanted to bind a text name of the role to it's RoleModel representation. That's basically what I did:

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

Then I had to add the following to the Global.asax:

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

And the usage in the view:

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

I hope this helps you.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文