Pregunta

¿Es posible vincular una relación de clave externa en mi modelo a una entrada de formulario?

Digamos que tengo una relación de uno a muchos entre Car y Manufacturer.quiero tener un formulario para actualizar Car que incluye una entrada selecta para configurar Manufacturer.Esperaba poder hacer esto usando el enlace de modelo incorporado, pero estoy empezando a pensar que tendré que hacerlo yo mismo.

La firma de mi método de acción se ve así:

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

El formulario publica los valores Nombre, Descripción y Fabricante, donde Fabricante es una clave principal de tipo. int.El nombre y la descripción se configuran correctamente, pero no el fabricante, lo cual tiene sentido ya que la carpeta del modelo no tiene idea de cuál es el campo PK.¿Eso significa que tendría que escribir una costumbre? IModelBinder ¿Que es consciente de esto?No estoy seguro de cómo funcionaría, ya que mis repositorios de acceso a datos se cargan a través de un contenedor IoC en cada Controller constructor.

¿Fue útil?

Solución

Aquí está mi opinión: este es un archivador de modelo personalizado que, cuando se le pregunta a GetPropertyValue, busca ver si la propiedad es un objeto del ensamblaje de mi modelo y tiene un IRepository < > registrado en mi NInject IKernel. Si puede obtener el IRepository de Ninject, lo usa para recuperar el objeto de clave externa.

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

}

obviamente podría sustituir Ninject por su contenedor DI y su propio tipo de repositorio.

Otros consejos

Seguramente cada auto solo tiene un fabricante. Si ese es el caso, entonces debe tener un campo de ID de fabricante al que pueda vincular el valor de la selección. Es decir, su selección debe tener el nombre del fabricante como texto y la identificación como valor. En su valor de ahorro, vincule ID de fabricante en lugar de Fabricante.

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

Con

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

Y

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

Tal vez sea tarde, pero puede usar un modelo de carpeta personalizada para lograr esto. Normalmente lo haría de la misma manera que @tvanofosson, pero tuve un caso en el que estaba agregando UserDetails a las tablas AspNetMembershipProvider. Como también uso solo POCO (y lo mapeo desde EntityFramework) no quería usar una identificación porque no estaba justificada desde el punto de vista comercial, así que creé un modelo solo para agregar / registrar usuarios. Este modelo tenía todas las propiedades para el usuario y también una propiedad de rol. Quería vincular un nombre de texto del rol a su representación RoleModel. Eso es básicamente lo que hice:

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

Luego tuve que agregar lo siguiente al Global.asax:

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

Y el uso en la vista:

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

Espero que esto te ayude.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top