Question

Est-il possible de lier une relation de clé étrangère de mon modèle à une entrée de formulaire?

Disons que j'ai une relation un-à-plusieurs entre Car et Manufacturer. Je souhaite disposer d’un formulaire de mise à jour int incluant une entrée de sélection pour le paramétrage IModelBinder. J'espérais pouvoir le faire en utilisant la liaison de modèle intégrée, mais je commence à penser que je devrai le faire moi-même.

La signature de ma méthode d'action ressemble à ceci:

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

Le formulaire affiche les valeurs Nom, Description et Fabricant, où Fabricant est une clé primaire de type Controller. Le nom et la description sont correctement configurés, mais pas le fabricant, ce qui est logique, car le classeur de modèles n'a aucune idée du champ PK. Est-ce que cela signifie que je devrais écrire une coutume <=> selon laquelle il est conscient de cela? Je ne sais pas comment cela fonctionnerait, car mes référentiels d'accès aux données sont chargés via un conteneur IoC sur chaque <=> constructeur.

Était-ce utile?

La solution

Voici ma conclusion. Il s'agit d'un classeur de modèle personnalisé qui, lorsqu'il est invité à GetPropertyValue, recherche si la propriété est un objet de mon assemblage de modèle et possède un IRepository < > enregistré dans mon NInject IKernel. S'il peut obtenir le dépôt IR auprès de Ninject, il l'utilise pour récupérer l'objet de clé étrangère.

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

}

vous pouvez évidemment remplacer Ninject par votre conteneur DI et votre propre type de référentiel.

Autres conseils

Certes, chaque voiture n’a qu’un seul fabricant. Si c'est le cas, vous devez avoir un champ ManufacturerID auquel vous pouvez associer la valeur de la sélection. C'est-à-dire que votre sélection doit avoir le nom du fabricant en tant que texte et l'id en tant que valeur. Dans votre valeur de sauvegarde, liez ManufacturerID plutôt que Manufacturer.

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

Avec

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

Et

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

C’est peut-être tardif, mais vous pouvez utiliser un classeur de modèle personnalisé pour y parvenir. Normalement, je le ferais de la même manière que @tvanofosson, mais j'avais un cas dans lequel j'ajoutais UserDetails aux tables AspNetMembershipProvider. Comme j'utilise également uniquement POCO (et le mappe depuis EntityFramework), je ne souhaitais pas utiliser d'identifiant, car il n'était pas justifié du point de vue commercial. J'ai donc créé un modèle uniquement pour ajouter / enregistrer des utilisateurs. Ce modèle possédait toutes les propriétés de l'utilisateur et une propriété de rôle. Je voulais lier un nom de texte du rôle à sa représentation RoleModel. C'est essentiellement ce que j'ai fait:

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

Ensuite, j'ai dû ajouter ce qui suit au fichier Global.asax:

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

Et l'utilisation dans la vue:

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

J'espère que cela vous aide.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top