Pergunta

É possível vincular um relacionamento de chave estrangeira no meu modelo para a entrada de formulário?

dizer que tenho um relacionamento um-para-muitos entre Car e Manufacturer. Eu quero ter um formulário para Car atualização que inclui uma selecção de entrada para a criação Manufacturer. Eu estava esperando para ser capaz de fazer isso usando o built-in modelo de ligação, mas eu estou começando a pensar que vou ter que fazer isso sozinho.

O meu método de ação assinatura esta aparência:

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

Os postos de formulário os valores nome, descrição e fabricante, onde Fabricante é uma chave primária do tipo int. Nome e descrição prepare-se adequadamente, mas não Fabricante, o que faz sentido desde o fichário de modelo não tem idéia do que o campo PK é. Isso significa que eu teria que escrever uma IModelBinder personalizado que tem conhecimento disso? Não tenho certeza como isso funciona desde meus repositórios de acesso a dados são carregados através de um contêiner IoC em cada construtor Controller.

Foi útil?

Solução

Aqui é a minha opinião - este é um fichário de modelo personalizado que quando solicitado a GetPropertyValue, olha para ver se a propriedade é um objeto do meu modelo de montagem, e tem um IRepository <> registrado na minha ninject Ikernel. Se ele pode obter o IRepository de Ninject, ele usa isso para recuperar o objeto de chave estrangeira.

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

}

Você poderia, obviamente, substituir Ninject para o seu recipiente DI e seu próprio tipo de repositório.

Outras dicas

Com certeza cada carro tem apenas um fabricante. Se for esse o caso, então você deve ter um campo ManufacturerID que você pode vincular o valor da seleção para. Ou seja, a sua escolha deve ter o nome do fabricante, uma vez que do texto e do id como o valor. Em seu salvar o valor, se ligam ManufacturerID em vez de Fabricante.

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

Com

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

E

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

Talvez seja uma tarde, mas você pode usar um fichário de modelo personalizado para alcançar este objectivo. Normalmente eu iria fazê-lo da mesma forma que @tvanofosson mas eu tive um caso em que eu estava adicionando UserDetails às tabelas AspNetMembershipProvider. Desde que eu também usar somente POCO (e mapeá-la a partir EntityFramework) Eu não queria usar um id porque não se justificava do ponto de vista do negócio assim que eu criei um modelo só para adicionar / registar utilizadores. Este modelo tinha todas as propriedades para o utilizador e uma propriedade Role também. Eu queria ligar um nome de texto do papel para a sua representação rolemodel. Isso é basicamente o que eu fiz:

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

Então eu tive que adicionar o seguinte para o Global.asax:

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

E o uso no modo de exibição:

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

Espero que isso ajude você.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top