Domanda

È possibile associare una relazione di chiave esterna sul mio modello a un input di modulo?

Supponi che io abbia una relazione uno-a-molti tra Car e Manufacturer. Voglio avere un modulo per l'aggiornamento int che includa un input selezionato per l'impostazione IModelBinder. Speravo di poterlo fare usando il binding del modello integrato, ma sto iniziando a pensare che dovrò farlo da solo.

La firma del mio metodo di azione è simile alla seguente:

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

Il modulo pubblica i valori Nome, Descrizione e Produttore, dove Produttore è una chiave primaria di tipo Controller. Nome e descrizione vengono impostati correttamente, ma non il produttore, il che ha senso poiché il raccoglitore del modello non ha idea di quale sia il campo PK. Ciò significa che dovrei scrivere un <=> personalizzato di cui sia a conoscenza? Non sono sicuro di come funzionerebbe poiché i miei repository di accesso ai dati vengono caricati attraverso un contenitore IoC su ciascun <=> costruttore.

È stato utile?

Soluzione

Ecco la mia opinione: questo è un raccoglitore di modelli personalizzato che, quando viene chiesto a GetPropertyValue, cerca di vedere se la proprietà è un oggetto dal mio assembly di modello e ha un IRepository < > registrato nel mio NInject IKernel. Se riesce a ottenere IRepository da Ninject, lo utilizza per recuperare l'oggetto chiave esterna.

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

}

puoi ovviamente sostituire Ninject per il tuo contenitore DI e il tuo tipo di repository.

Altri suggerimenti

Sicuramente ogni auto ha un solo produttore. In tal caso, dovresti avere un campo ManufacturerID a cui puoi associare il valore della selezione. Cioè, la tua selezione dovrebbe avere il nome del produttore come testo e l'id come valore. Nel tuo valore di risparmio, associa ManufacturerID piuttosto che Manufacturer.

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

Con

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)

Forse è tardi, ma puoi usare un raccoglitore di modelli personalizzato per raggiungere questo obiettivo. Normalmente lo farei allo stesso modo di @tvanofosson ma ho avuto un caso in cui stavo aggiungendo UserDetails alle tabelle AspNetMembershipProvider. Dato che utilizzo anche solo POCO (e lo mappa da EntityFramework) non volevo usare un ID perché non era giustificato dal punto di vista aziendale, quindi ho creato un modello solo per aggiungere / registrare utenti. Questo modello aveva tutte le proprietà per l'utente e anche una proprietà Role. Volevo associare un nome di testo del ruolo alla sua rappresentazione RoleModel. Questo è fondamentalmente quello che ho fatto:

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

Quindi ho dovuto aggiungere quanto segue a Global.asax:

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

E l'uso nella vista:

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

Spero che questo ti aiuti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top