ASP.NET MVC model binding foreign key relationship

Is it possible to associate a foreign key relationship with my model with form input?

Say I have a one-to-many relationship between Car and Manufacturer . I want to have a form for updating Car , which includes the choice of input for installing Manufacturer . I was hoping I could do this using the built-in model binding, but I'm starting to think that I will have to do this myself.

My action method signature is as follows:

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

The form publishes the values ​​Name, Description and Manufacturer, where Manufacturer is the primary key of type int . The name and description are set correctly, but not by the manufacturer, which makes sense, since the connecting device of the model does not know what the PC field is. Does this mean that I would have to write a custom IModelBinder so that he knew about it? I'm not sure how this will work, since my data access stores are loaded through the IoC container for each Controller constructor.

+7
asp.net-mvc model-binding
source share
3 answers

Here, my trick is a custom mediator that, when requested by GetPropertyValue, checks to see if the property is an object from my model assembly and has an IRepository <> registered in my NInject IKernel. If he can get an IRepository from Ninject, he uses this 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 can obviously replace Ninject for your DI container and your own repository type.

+6
source share

Of course, each car has only one manufacturer. If in this case you should have a ManufacturerID field, which you can bind to the select value. That is, your choice should have the name of the manufacturer as its text, and the identifier as the value. In your saved value, link the Manufacturer, not the Manufacturer.

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

FROM

 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) 
+3
source share

It may be late, but you can use a custom mediator to do this. Usually I did this the same way as @tvanofosson, but I had a case when I added UserDetails to AspNetMembershipProvider tables. Since I also use only POCO (and map it to EntityFramework), I did not want to use the identifier because it was not justified from a business point of view, so I created a model only for adding / registering users. This model had all the user properties and the Role property. I wanted to associate the text name of the role with the RoleModel view. This is 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 Global.asax:

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

And use in view:

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

Hope this helps you.

+2
source share

All Articles