2010-03-27 6 views
0

Je suis sûr que cela a déjà été répondu, mais j'ai passé les trois dernières heures à la recherche d'une solution acceptable et je n'ai rien trouvé, donc je m'excuse pour ce que je suis Bien sûr, c'est une répétition.Reliure avec Relation Parent/Enfant

J'ai deux objets de domaine, joueur et position. Les joueurs ont une position. Mes objets de domaine sont des POCO liés à ma base de données avec NHibernate. J'ai une action Ajouter qui prend un joueur, donc j'utilise la liaison de modèle intégrée. À mon avis, j'ai une liste déroulante qui permet à un utilisateur de sélectionner la position du joueur. La valeur de la liste déroulante est l'identifiant de la position. Tout est correctement rempli, sauf que mon objet Position échoue à la validation (ModelState.IsValid) car, au moment de la liaison du modèle, il n'a qu'un ID et aucun de ses autres attributs requis.

Quelle est la solution préférée pour résoudre ce problème avec ASP.NET MVC 2?

Solutions J'ai essayé ...

  1. Fetch la position de la base de données sur la base Id avant ModelState.IsValid est appelé dans l'action Add de mon contrôleur. Je ne peux pas obtenir le modèle pour réexécuter la validation, donc ModelState.IsValid retourne toujours false.
  2. Créez un ModelBinder personnalisé qui hérite du classeur par défaut et récupère la Position à partir de la base de données après l'appel du classeur de base. Le ModelBinder semble faire la validation, donc si j'utilise quelque chose du classeur par défaut, je suis arrosé. Ce qui signifie que je dois complètement rouler mon propre classeur et saisir toutes les valeurs de la forme ... cela semble vraiment faux et inefficace pour un tel cas d'utilisation commun.

Solutions Je pense pourrait fonctionner, je ne peux pas comprendre comment faire ...

  1. Désactiver la validation de la classe de position quand il est utilisé dans le lecteur.
  2. Écrire un modèle personnalisé exploite le classeur par défaut pour la plupart des liaisons de propriétés, mais permet d'obtenir la position de la base de données AVANT que le classeur par défaut ne soit validé.

Alors, comment le reste de vous résoudre ce problème?

Merci,

Dan

post-scriptum À mon avis, avoir un PositionId sur Player juste pour ce cas n'est pas une bonne solution. Il doit être résoluble d'une manière plus élégante.

Répondre

3

Non seulement à ce problème particulier, mais en général, je voudrais créer un ViewModel distinct au lieu de laisser la vue avoir le modèle de domaine. Donc, dans votre cas, vous n'avez pas besoin de surexposer votre modèle de domaine (et d'obtenir des choses dont vous n'avez pas besoin) à la vue, et, néanmoins, désactiver la validation est probablement la pire solution

+0

Donc, fondamentalement, je dois faire des DTO pour toutes mes vues. Cela semble assez inefficace pour moi. Est-ce que quelqu'un d'autre a une solution? – DFX

+0

J'ai eu la même pensée quand j'en ai entendu parler au début. Mais les aspects positifs du test et de la séparation des préoccupations semblent toujours gagner. Pour moi, il est souvent plus rapide de créer un autre modèle que de déboguer un domaine spécifique ou de créer des modélisations personnalisées. –

0

Créer une ModelBinder personnalisé qui peut valider ce cas particulier pour vous.

+0

Xeb, j'ai du mal à trouver de bonnes informations sur les ModelBinders personnalisés, pensez-vous pouvoir me diriger dans le bon sens? – DFX

0

J'ai eu le même problème dans l'une de mes applications. Je l'ai résolu en créant un IModelBinder personnalisé héritant de DefaultModelBinder et enregistré avec le type particulier que je lie.

L'astuce consiste à utiliser qui crée seulement une référence typée à l'entité sans interroger la base de données.(Read more about session.Load here in Ayende's blog)

/// <summary> 
    /// Base for binding references. Usually displayed in dropdowns 
    /// </summary> 
    public class ReferenceBinder<T> : DefaultModelBinder 
     where T : class 
    { 

     private readonly ISession session; 

     public ReferenceBinder(ISession session) 
     { 
      this.session = session; 
     } 


     public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) 
     { 

      var idName = CreateSubPropertyName(bindingContext.ModelName, "ID"); 

      ValueProviderResult result = bindingContext.ValueProvider.GetValue(idName); 

      int value; 
      return (int.TryParse(result.AttemptedValue, out value)) ? this.session.Load<T>(value) : null; 

     } 


    } 

Dans votre exemple, vous feriez quelque chose comme ça dans Global.asax:

ModelBinders.Binders.Add(typeof(Position), new ReferenceBinder<Position>(<pass your current session implementation or use DI/IoC>)); 

(J'utilise le château de Windsor pour créer le liant réelle et remplir la session dans ma mise en œuvre)

en supposant que votre modèle de domaine est:

 public class Player { 
      public virtual Position Position {get;set;} 
     } 

     public class Position { 
      public virtual int ID {get;private set;} 
     } 

Et votre postback ressemble à ceci:

"Position.ID" = <id from dropdown> 

EDIT: Vous devriez probablement utiliser une approche DTO avec des modèles de vue dédiés. Je l'ai appris plus tard. Have a look here for a quick start.