50 votes

Asp.Net MVC 2 - Lie la propriété d'un modèle à une autre valeur nommée

Mise à jour (30 avril 2012) - Note pour les gens de tomber sur cette question à partir de recherches etc - accepté la réponse n'est pas comment j'ai fini par le faire - mais je l'ai quitté, il a accepté parce qu'elle peut avoir travaillé dans certains cas. Ma propre réponse contient la dernière solution que j'ai utilisée, qui est réutilisable et s'applique à tout projet.

Il est également confirmée à travailler en v3 et v4 de la MVC.

J'ai le texte suivant le type de modèle (le nom de la classe et de ses propriétés ont été modifiées afin de protéger leur identité):

public class MyExampleModel
{
  public string[] LongPropertyName { get; set; }
}

Cette propriété est alors lié à un groupe (>150) de cases à cocher, où chacun du nom de l'entrée est bien sûr, LongPropertyName.

Le formulaire soumet à l'url avec un HTTP GET, et dire que l'utilisateur sélectionne trois de ces cases - l'url sera la chaîne de requête, ?LongPropertyName=a&LongPropertyName=b&LongPropertyName=c

Gros problème c'est que si j'ai tout sélectionner (ou même un peu plus de la moitié!) les cases à cocher, je dépasse le maximum de la longueur de chaîne appliquée par le filtre de requête sur IIS!

Je ne veux pas prolonger ce - si je veux une façon de couper vers le bas cette chaîne de requête (je sais que je peux simplement passer à un POSTE - mais même si j'ai encore envie de minimiser la quantité de peluches dans les données envoyées par le client).

Ce que je veux faire est de l' LongPropertyName lié à tout simplement 'L', de sorte que la chaîne de requête deviendrait ?L=a&L=b&L=c mais sans changer le nom de la propriété dans le code.

Le type en question dispose déjà d'un modèle de liaison personnalisé (dérivant de DefaultModelBinder), mais qu'il est attaché à sa classe de base - donc, je ne veux pas mettre de code dans pour une classe dérivée. Toutes les propriétés de la liaison est effectuée par la norme DefaultModelBinder logique, que je connais utilise TypeDescriptors et de la Propriété des Descripteurs etc à partir du Système.ComponentModel.

J'ai été un peu en espérant qu'il peut être un attribut je pourrais demander à la propriété pour faire ce travail - qui est là? Ou devrais-je être à la recherche à la mise en œuvre de ICustomTypeDescriptor?

85voto

Andras Zoltan Points 24996

En réponse à michaelalm de réponse et demande - voici ce que j'ai fini par faire. J'ai laissé l'original de la réponse cochée principalement de courtoisie, car l'une des solutions proposées par Nathan aurait travaillé.

La sortie de cette est un remplacement pour l' DefaultModelBinder classe qui vous pouvez vous inscrire à l'échelle mondiale (permettant ainsi à tous les types de modèle de profiter de l'aliasing) ou de manière sélective pour hériter des classeurs de modèles personnalisés.

Tout commence, de manière prévisible avec:

/// <summary>
/// Allows you to create aliases that can be used for model properties at
/// model binding time (i.e. when data comes in from a request).
/// 
/// The type needs to be using the DefaultModelBinderEx model binder in 
/// order for this to work.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class BindAliasAttribute : Attribute
{
  public BindAliasAttribute(string alias)
  {
    //ommitted: parameter checking
    Alias = alias;
  }
  public string Alias { get; private set; }
}

Et puis, nous avons cette classe:

internal sealed class AliasedPropertyDescriptor : PropertyDescriptor
{
  public PropertyDescriptor Inner { get; private set; }

  public AliasedPropertyDescriptor(string alias, PropertyDescriptor inner)
    : base(alias, null)
  {
    Inner = inner;
  }

  public override bool CanResetValue(object component)
  {
    return Inner.CanResetValue(component);
  }

  public override Type ComponentType
  {
    get { return Inner.ComponentType; }
  }

  public override object GetValue(object component)
  {
    return Inner.GetValue(component);
  }

  public override bool IsReadOnly
  {
    get { return Inner.IsReadOnly; }
  }

  public override Type PropertyType
  {
    get { return Inner.PropertyType; }
  }

  public override void ResetValue(object component)
  {
    Inner.ResetValue(component);
  }

  public override void SetValue(object component, object value)
  {
    Inner.SetValue(component, value);
  }

  public override bool ShouldSerializeValue(object component)
  {
    return Inner.ShouldSerializeValue(component);
  }
}

Cette procuration un " bon " PropertyDescriptor que l'on trouve normalement par l' DefaultModelBinder mais présente en son nom comme pseudonyme.

Ensuite, nous avons le nouveau modèle de classeur de la classe:

public class DefaultModelBinderEx : DefaultModelBinder
{
  protected override System.ComponentModel.PropertyDescriptorCollection
    GetModelProperties(ControllerContext controllerContext, 
                      ModelBindingContext bindingContext)
  {
    var toReturn = base.GetModelProperties(controllerContext, bindingContext);

    List<PropertyDescriptor> additional = new List<PropertyDescriptor>();

    //now look for any aliasable properties in here
    foreach (var p in 
      this.GetTypeDescriptor(controllerContext, bindingContext)
      .GetProperties().Cast<PropertyDescriptor>())
    {
      foreach (var attr in p.Attributes.OfType<BindAliasAttribute>())
      {
        additional.Add(new AliasedPropertyDescriptor(attr.Alias, p));

        if (bindingContext.PropertyMetadata.ContainsKey(p.Name))
          bindingContext.PropertyMetadata.Add(attr.Alias,
                bindingContext.PropertyMetadata[p.Name]);
      }
    }

    return new PropertyDescriptorCollection
      (toReturn.Cast<PropertyDescriptor>().Concat(additional).ToArray());
  }
}

Et puis techniquement, c'est tout là est à lui. Vous pouvez maintenant enregistrer ce DefaultModelBinderEx classe que le défaut à l'aide de la solution posté la réponse dans cette SORTE: Changer le modèle de classeur par défaut dans asp.net MVC, ou vous pouvez l'utiliser comme base pour votre propre modèle de classeur.

Une fois que vous avez sélectionné votre modèle pour la façon dont vous voulez que le liant à coup de pied, il vous suffit de l'appliquer à un type de modèle comme suit:

public class TestModelType
{
    [BindAlias("LPN")]
    //and you can add multiple aliases
    [BindAlias("L")]
    //.. ad infinitum
    public string LongPropertyName { get; set; }
}

La raison que j'ai choisi ce code était parce que je voulais quelque chose qui pourrait fonctionner avec un type personnalisé descripteurs ainsi que d'être capable de travailler avec n'importe quel type. Aussi, je voulais la valeur de fournisseur de système pour être utilisé encore dans la recherche de modèle de valeurs de propriété. J'ai donc changé les méta-données qui l' DefaultModelBinder voit quand il commence de liaison. C'est un peu plus longue haleine approche - mais, conceptuellement, c'est de faire à la méta données de niveau exactement ce que vous voulez qu'il fasse.

Un potentiellement intéressant, et un peu ennuyeux, effet secondaire si l' ValueProvider contient des valeurs pour plus d'un alias ou un alias et de la propriété en son nom. Dans ce cas, seul l'un de l'extrait valeurs seront utilisées. Difficile de penser à un moyen de les fusionner le tout dans un type de façon sécuritaire lorsque vous êtes juste à travailler avec objects si. Ceci est similaire, bien que, pour fournir une valeur dans un formulaire post et la chaîne de requête - et je ne suis pas sûr exactement ce que MVC dans ce scénario - mais je ne pense pas qu'il est recommandé de pratiquer.

Un autre problème est, bien sûr, que vous ne devez pas créer un alias qui est égal à un autre alias, ou en effet le nom d'une propriété réelle.

J'aime appliquer mes classeurs de modèle, en général, à l'aide de l' CustomModelBinderAttribute classe. Le seul problème avec ce peut être si vous avez besoin de tirer du type de modèle et de le modifier de liaison du comportement depuis l' CustomModelBinderAttribute est héritée dans l'attribut de la recherche effectuée par MVC.

Dans mon cas, ce n'est pas grave, je suis l'élaboration d'un nouveau cadre de site et je suis capable de pousser la nouvelle extensibilité dans ma base de liants en utilisant d'autres mécanismes pour répondre à ces nouveaux types; mais ce ne sera pas le cas pour tout le monde.

20voto

Nathan Taylor Points 13582

Vous pouvez utiliser le BindAttribute pour accomplir cette tâche.

public ActionResult Submit([Bind(Prefix = "L")] string[] longPropertyName) {

}

Mise à jour

Depuis les années longPropertyName' le paramètre est une partie du modèle objet, et non pas un paramètre indépendant de l'action du contrôleur, vous avez un couple de d'autres choix.

Vous pouvez garder le modèle et la propriété des paramètres indépendants à votre action, puis de fusionner manuellement les données dans la méthode d'action.

public ActionResult Submit(MyModel myModel, [Bind(Prefix = "L")] string[] longPropertyName) {
    if(myModel != null) {
        myModel.LongPropertyName = longPropertyName;
    }
}

Une autre option serait de mettre en œuvre un Modèle de liaison personnalisé qui effectue la valeur de paramètre d'affectation (comme ci-dessus) à la main, mais qui est probablement exagéré. Voici un exemple d'un, si vous êtes intéressés: les Drapeaux de l'Énumération Modèle de Classeur.

4voto

michaelalm Points 120

serait-ce une solution semblable à la vôtre Andras? J'espère que vous pourrez aussi poster votre réponse.

méthode du contrôleur

 public class MyPropertyBinder : DefaultModelBinder
{
    protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor)
    {
        base.BindProperty(controllerContext, bindingContext, propertyDescriptor);

        for (int i = 0; i < propertyDescriptor.Attributes.Count; i++)
        {
            if (propertyDescriptor.Attributes[i].GetType() == typeof(BindingNameAttribute))
            {                    
                // set property value.
                propertyDescriptor.SetValue(bindingContext.Model, controllerContext.HttpContext.Request.Form[(propertyDescriptor.Attributes[i] as BindingNameAttribute).Name]);
                break;
            }
        }
    }
}
 

Attribut

 public class BindingNameAttribute : Attribute
{
    public string Name { get; set; }

    public BindingNameAttribute()
    {

    }
}
 

ViewModel

 public class EmployeeViewModel
{                    

    [BindingName(Name = "txtName")]
    public string TestProperty
    {
        get;
        set;
    }
}
 

puis d'utiliser le classeur dans le contrôleur

 [HttpPost]
public ActionResult SaveEmployee(int Id, [ModelBinder(typeof(MyPropertyBinder)] EmployeeViewModel viewModel)
{
        // do stuff here
}
 

la valeur du formulaire txtName doit être définie sur TestProperty.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X