0

J'ai créé un modèle de référentiel au-dessus du cadre d'entité ado.net. Quand j'ai essayé d'implémenter StructureMap pour découpler mes objets, j'ai continué à obtenir StackOverflowException (boucle infinie?). Voici ce que le modèle ressemble:Problèmes liés à mon modèle de référentiel MVC et à StructureMap

IEntityRepository où TEntity: classe Définit les membres CRUD de base

MyEntityRepository: IEntityRepository Met en œuvre les membres CRUD

IEntityService où TEntity: classe Définit les membres CRUD qui renvoient les types communs pour chaque membre.

MyEntityService: IEntityService Utilise le référentiel pour récupérer les données et renvoyer un type commun comme résultat (IList, bool et etc)

Le problème semble être avec ma couche de service. Plus spécifiquement avec les constructeurs.

public PostService(IValidationDictionary validationDictionary) 
     : this(validationDictionary, new PostRepository()) 
    { } 

    public PostService(IValidationDictionary validationDictionary, IEntityRepository<Post> repository) 
    { 
     _validationDictionary = validationDictionary; 
     _repository = repository; 
    } 

Depuis le contrôleur, je passe un objet qui implémente IValidationDictionary. Et j'appelle explicitement le second constructeur pour initialiser le dépôt.

C'est ce que les constructeurs de contrôleur ressemblent (le premier crée une instance de l'objet de validation):

public PostController() 
    { 
     _service = new PostService(new ModelStateWrapper(this.ModelState)); 
    } 

    public PostController(IEntityService<Post> service) 
    { 
     _service = service; 
    } 

Tout fonctionne si je ne passe pas mon objet IValidationDictionary référence, auquel cas le premier Le constructeur du contrôleur serait supprimé et l'objet de service n'aurait qu'un seul constructeur acceptant l'interface du référentiel comme paramètre.

J'apprécie toute aide avec ceci :) Merci.

Répondre

8

Il semble que la référence circulaire avait à voir avec le fait que la couche de service dépendait ModelState du contrôleur et le contrôleur en fonction de la couche de service.

je devais réécrire ma couche de validation pour obtenir ce travail. Voici ce que j'ai fait.

Define interface validateur générique comme ci-dessous:

public interface IValidator<TEntity> 
{ 
    ValidationState Validate(TEntity entity); 
} 

Nous voulons être en mesure de retourner une instance de ValidationState qui, évidemment, définit l'état de validation. Notez que nous avons une collection d'erreurs fortement typée que nous devons également définir. La collection va être constituée d'objets ValidationError contenant le nom de la propriété de l'entité que nous validons et le message d'erreur qui lui est associée. Cela suit simplement l'interface ModelState standard.

public class ValidationErrorCollection : Collection<ValidationError> 
{ 
    public void Add(string property, string message) 
    { 
     Add(new ValidationError(property, message)); 
    } 
} 

Et voici ce que le ValidationError ressemble:

public class ValidationError 
{ 
    private string _property; 
    private string _message; 

    public string Property 
    { 
     get 
     { 
      return _property; 
     } 

     private set 
     { 
      _property = value; 
     } 
    } 

    public string Message 
    { 
     get 
     { 
      return _message; 
     } 

     private set 
     { 
      _message = value; 
     } 
    } 

    public ValidationError(string property, string message) 
    { 
     Property = property; 
     Message = message; 
    } 
} 

Le reste de c'est magique StructureMap. Nous devons créer une couche de service de validation qui va localiser les objets de validation et valider notre entité.Je voudrais définir une interface pour cela, puisque je veux que quiconque utilisant le service de validation soit complètement inconscient de la présence de StructureMap. En outre, je pense que Sprinkling ObjectFactory.GetInstance() n'importe où en dehors de la logique du bootstrapper une mauvaise idée. Le garder centralisé est un bon moyen d'assurer une bonne maintenabilité. Quoi qu'il en soit, j'utilise le modèle décorateur ici:

public interface IValidationService 
{ 
    ValidationState Validate<TEntity>(TEntity entity); 
} 

que nous mettons enfin:

public class ValidationService : IValidationService 
{ 
    #region IValidationService Members 

    public IValidator<TEntity> GetValidatorFor<TEntity>(TEntity entity) 
    { 
     return ObjectFactory.GetInstance<IValidator<TEntity>>(); 
    } 

    public ValidationState Validate<TEntity>(TEntity entity) 
    { 
     IValidator<TEntity> validator = GetValidatorFor(entity); 

     if (validator == null) 
     { 
      throw new Exception("Cannot locate validator"); 
     } 

     return validator.Validate(entity); 
    } 

    #endregion 
} 

Je vais être en utilisant le service de validation dans mon contrôleur. Nous pourrions le déplacer vers la couche de service et avoir StructureMap utiliser l'injection de propriété pour injecter une instance de ModelState du contrôleur à la couche de service, mais je ne veux pas que la couche de service soit couplée avec ModelState. Et si nous décidions d'utiliser une autre technique de validation? C'est pourquoi je préfère le mettre dans le contrôleur. Voici ce que mon contrôleur ressemble à:

public class PostController : Controller 
{ 
    private IEntityService<Post> _service = null; 
    private IValidationService _validationService = null; 

    public PostController(IEntityService<Post> service, IValidationService validationService) 
    { 
     _service = service; 
     _validationService = validationService; 
    } 
} 

Ici je suis injectais ma couche de service et les instances de service à l'aide validaton StructureMap. Donc, nous devons enregistrer les deux dans le registre StructureMap:

ForRequestedType<IValidationService>() 
     .TheDefaultIsConcreteType<ValidationService>(); 

    ForRequestedType<IValidator<Post>>() 
      .TheDefaultIsConcreteType<PostValidator>(); 

C'est tout. Je ne montre pas comment j'implémente mon PostValidator, mais c'est simplement l'implémentation de l'interface IValidator et la définition de la logique de validation dans la méthode Validate(). Il ne vous reste plus qu'à appeler votre instance de service de validation pour récupérer le validateur, appeler la méthode validate sur votre entité et écrire les erreurs dans ModelState.

[AcceptVerbs(HttpVerbs.Post)] 
    public ActionResult Create([Bind(Exclude = "PostId")] Post post) 
    { 
     ValidationState vst = _validationService.Validate<Post>(post); 

     if (!vst.IsValid) 
     { 
      foreach (ValidationError error in vst.Errors) 
      { 
       this.ModelState.AddModelError(error.Property, error.Message); 
      } 

      return View(post); 
     } 

     ... 
    } 

espoir j'ai aidé quelqu'un avec cette :)

0

Juste une question rapide à ce sujet. Cela m'a beaucoup aidé, alors merci d'avoir répondu, mais je me demandais dans quel espace de noms existe TEntity? Je vois que Colletion (TEntity) a besoin de System.Collections.ObjectModel. Mon fichier se compile sans plus, mais je vois votre référence TEntity surlignée en bleu ce qui suggère qu'il a un type de classe, le mien est noir dans Visual Studio. J'espère que vous pouvez aider. Je suis plutôt désireux de faire fonctionner ça.

Avez-vous trouvé un moyen de séparer la validation dans la couche de service? Mon instinct me dit que la validation dans le contrôleur est un peu malodorant mais j'ai regardé haut et bas pour trouver un moyen de transmettre les messages d'erreur de validation au contrôleur sans coupler étroitement la couche de service au contrôleur et ne trouve rien. :(

Encore une fois, merci pour le grand poste!

Lloyd

+0

La coloration syntaxique est un peu hors sur StackOverflow :) TEntity est un type générique , pas un objet de classe. Il est simplement noir dans Visual Studio. J'utilise TEntity pour simplement passer le type d'objet que je veux valider. Je suis d'accord avec vous sur la validation dans le contrôleur. Je n'ai pas trouvé de meilleure façon de valider cela, ce qui me donnerait beaucoup de souplesse. En procédant de cette façon, je peux utiliser n'importe quel cadre de validation que je veux dans mon validateur d'entité. –

+0

Maintenant, je n'y ai pas beaucoup réfléchi, mais ce qui pourrait fonctionner est d'utiliser le modèle d'adaptateur pour écrire un adaptateur pour ModelState. Donc, essentiellement, vous devez valider votre entité, obtenir l'objet ValidationState et utiliser l'adaptateur pour le convertir en ModelState. Si vous l'avez suffisamment abstrait, vous pouvez utiliser StructureMap pour injecter l'adaptateur de votre choix. Je ne suis pas sûr à quel point cela serait lié à votre contrôleur, mais je suis sûr que vous pourriez le découpler assez bien. De cette façon, votre service de validation pourrait probablement être utilisé au sein de vos modèles commerciaux/entités ou de la couche de service. Je vais voir si je peux faire quelque chose avec ça. –

+0

Je regardais à la suite de cela, http://www.asp.net/Learn/mvc/tutorial-38-cs.aspx. A l'origine, je n'avais pas pensé à l'option car je pensais qu'elle couplerait trop ma couche de service à MVC mais à la minute, pour faire avancer mon projet, ça ressemble au meilleur d'un mauvais groupe. Je suis très désireux de trouver une alternative plus lâche cependant. Si vous venez avec quelque chose me déposer un email à lloyd phillips tout un mot à xtra dot co dot nz. Cordialement Lloyd –

1

J'ai utilisé une solution similaire impliquant un implementor générique de IValidationDictionary utilise un StringDictionary puis copié les erreurs de ce retour à l'état de modèle le contrôleur.

Interface pour validationdictionary

public interface IValidationDictionary 
    { 
     bool IsValid{get;} 
     void AddError(string Key, string errorMessage); 
     StringDictionary errors { get; } 
    } 

mise en oeuvre de validation dictionar y sans référence au modèle d'État ou toute autre chose si StructureMap peut créer facilement

public class ValidationDictionary : IValidationDictionary 
{ 

    private StringDictionary _errors = new StringDictionary(); 

    #region IValidationDictionary Members 

    public void AddError(string key, string errorMessage) 
    { 
     _errors.Add(key, errorMessage); 
    } 

    public bool IsValid 
    { 
     get { return (_errors.Count == 0); } 
    } 

    public StringDictionary errors 
    { 
     get { return _errors; } 
    } 

    #endregion 
} 

code dans le contrôleur pour copier les erreurs du dictionnaire dans l'état du modèle. Ce serait probablement mieux comme une fonction d'extension du contrôleur.

protected void copyValidationDictionaryToModelState() 
{ 
    // this copies the errors into viewstate 
    foreach (DictionaryEntry error in _service.validationdictionary.errors) 
    { 
     ModelState.AddModelError((string)error.Key, (string)error.Value); 
    } 
} 

Code bootstrapping ainsi est comme ce

public static void BootstrapStructureMap() 
{ 
    // Initialize the static ObjectFactory container 
    ObjectFactory.Initialize(x => 
    { 
     x.For<IContactRepository>().Use<EntityContactManagerRepository>(); 
     x.For<IValidationDictionary>().Use<ValidationDictionary>(); 
     x.For<IContactManagerService>().Use<ContactManagerService>(); 
    }); 
} 

et le code pour créer des contrôleurs est comme ce

public class IocControllerFactory : DefaultControllerFactory 
{ 
    protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) 
    { 
     return (Controller)ObjectFactory.GetInstance(controllerType); 
    } 
} 
Questions connexes