是否可以将模型上的外键关系绑定到表单输入?
说我Car
和Manufacturer
之间有一对多的关系。我想要一个用于更新Car
的表单,其中包含用于设置Manufacturer
的选择输入。我希望能够使用内置的模型绑定来做到这一点,但我开始认为我必须自己做。
我的操作方法签名如下所示:
public JsonResult Save(int id, [Bind(Include="Name, Description, Manufacturer")]Car car)
表单会发布值Name,Description和Manufacturer,其中Manufacturer是int
类型的主键。名称和描述设置正确,但不是制造商,这是有道理的,因为模型绑定器不知道PK字段是什么。这是否意味着我必须编写一个它知道这个的自定义IModelBinder
?我不确定这是如何工作的,因为我的数据访问存储库是通过每个Controller
构造函数上的IoC容器加载的。
答案 0 :(得分:6)
这是我的看法 - 这是一个自定义模型绑定器,当被问到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容器和您自己的存储库类型。
答案 1 :(得分:3)
当然每辆车只有一个制造商。如果是这种情况,那么你应该有一个ManufacturerID字段,你可以将select的值绑定到。也就是说,您的选择应该将制造商名称作为文本,将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)
答案 2 :(得分: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))%>
我希望这会对你有所帮助。