2010-10-06 3 views
9

J'utilise une approche similaire à celle de ce ASP.NET MVC tutorial où vous passez un wrapper autour de la collection ModelState d'un contrôleur dans une classe de validation afin que le contrôleur puisse accéder aux informations d'erreur.Injecter la propriété ASP.NET MVC Controller dans la dépendance de la couche de service?

Voici un exemple mijoté:

interface IProductValidator { 
    void Validate(Product item); 
} 

class ProductValidator { 
    // constructor 
    public ProductValidator(ModelStateWrapper validationDictionary) { } 
} 

interface IProductService { 
    void AddProduct(); 
} 

public class ProductService : IProductService { 
    // constructor 
    public ProductService(IProductValidator validator) { } 
} 

Utilisation du contenant Castle Windsor pour IoC/DI, comment puis-je créer le IProductService? En règle générale, j'ai:

MvcApplication.IocContainer.Resolve<IProductService>() 

mais ce n'est pas en mesure d'injecter la valeur de la propriété ModelState du contrôleur dans le constructeur de ProductValidator. Je pourrais probablement câbler cela en utilisant les paramètres du constructeur, mais cela semble vraiment moche.

+0

Avez-vous quitté: http://stackoverflow.com/questions/2077055/ioc-on-ivalidationdictionary-with-castle-windsor – bzarah

+0

Voilà une question assez similaire: dans mon cas, je n'essaie même pas pour instancier un validateur-- Je veux que le conteneur le résolve. En tout cas, la question à laquelle vous êtes lié n'a aucune réponse pour moi. Je vais laisser cela et espérer un aperçu supplémentaire. – user403830

+0

J'espérais qu'il y avait un moyen d'obtenir le ControllerContext actuel de manière statique, tout comme vous pouvez obtenir le HttpContext actuel avec HttpContext.Current. Malheureusement, je n'ai pas pu en trouver un. Une autre idée serait d'avoir une méthode supplémentaire sur IProductValidator qui accepterait un "ModelStateWrapper" et copierait les erreurs de validation là-bas. Pas aussi bien que Dependency Injection, mais cela devrait fonctionner assez facilement. – PatrickSteele

Répondre

2

Je suppose que vous voulez que le modèle transmis pour injecter automatiquement des erreurs dans votre modèle? IMHO, ModelState devrait rester là où il est, et vous apporter les erreurs de validation. Voici comment je gère les erreurs à titre d'exemple. Je ne dis pas que c'est la meilleure façon ou la seule façon, mais c'est une façon dont votre couche de validation n'a pas besoin de savoir qui ou quoi consomme les erreurs de validation. Tout d'abord, dans ma page, j'utilise System.ComponentModel.DataAnnotations pour les règles de validation. Voici ma classe de compte, par exemple.

public class Account : CoreObjectBase<Account> 
{ 
    public virtual int AccountId { get; set; } 

    [Required(ErrorMessage = "Email address is required.")] 
    public virtual string EmailAddress { get; set; } 

    [Required(ErrorMessage = "A password is required.")] 
    public virtual string Password { get; set; } 
} 

Parce que je veux être en mesure de lancer moi-même la validation (en dehors de MVC le faire sur son propre), je devais mettre en œuvre ma propre validateur.

public class Validator<T> where T : CoreObjectBase<T> 
{ 
    public ValidationResponse Validate(T entity) 
    { 
     var validationResults = new List<ValidationResult>(); 
     var context = new ValidationContext(entity, null, null); 
     var isValid = Validator.TryValidateObject(entity, context, validationResults); 

     return new ValidationResponse(validationResults.ToArray()); 
    } 
} 

Voici le ValidationResult que je passe en retrait

[Serializable] 
public class ValidationResponse 
{ 
    public IList<ValidationResult> Violations { get; private set; } 

    public IList<ErrorInfo> Errors { get; private set; } 

    public bool HasViolations 
    { 
     get { return Violations.Count > 0; } 
    } 

    public ValidationResponse(params ValidationResult[] violations) 
    { 
     Violations = new List<ValidationResult>(violations); 

     var errors = from v in Violations 
        from n in v.MemberNames 
        select new ErrorInfo(n, v.ErrorMessage); 

     Errors = errors.ToList(); 
    } 

} 

ErrorInfo est une classe très basique avec des informations sur mon erreur

[Serializable] 
public class ErrorInfo 
{ 
    public string ErrorMessage { get; private set; } 
    public object Object { get; private set; } 
    public string PropertyName { get; private set; } 

    public ErrorInfo(string propertyName, string errorMessage) 
     : this(propertyName, errorMessage, null) 
    { 

    } 

    public ErrorInfo(string propertyName, string errorMessage, object onObject) 
    { 
     PropertyName = propertyName; 
     ErrorMessage = errorMessage; 
     Object = onObject; 
    } 
} 

pour envelopper cette validation toute agréable et soigné avec mes classes poco, j'hérite d'une classe de base. Pour que la validation le rende générique là où l'enfant hérité doit dire à la classe de base c'est le type. Cela semble circulaire, mais cela fonctionne.

[Serializable] 
public class CoreObjectBase<T> : IValidatable where T : CoreObjectBase<T> 
{ 
    #region IValidatable Members 

    public virtual bool IsValid 
    { 
     get 
     { 
      // First, check rules that always apply to this type 
      var result = new Validator<T>().Validate((T)this); 

      // return false if any violations occurred 
      return !result.HasViolations; 
     } 
    } 

    public virtual ValidationResponse ValidationResults 
    { 
     get 
     { 
      var result = new Validator<T>().Validate((T)this); 
      return result; 
     } 
    } 

    public virtual void Validate() 
    { 
     // First, check rules that always apply to this type 
     var result = new Validator<T>().Validate((T)this); 

     // throw error if any violations were detected 
     if (result.HasViolations) 
      throw new RulesException(result.Errors); 
    } 

    #endregion 
} 

Enfin, comme vous pouvez le voir, ma validation déclenche une exception RulesException. Cette classe est un wrapper pour toutes les erreurs.

[Serializable] 
public class RulesException : Exception 
{ 
    public IEnumerable<ErrorInfo> Errors { get; private set; } 

    public RulesException(IEnumerable<ErrorInfo> errors) 
    { 
     Errors = errors != null ? errors : new List<ErrorInfo>(); 
    } 

    public RulesException(string propertyName, string errorMessage) : 
     this(propertyName, errorMessage, null) 
    { 

    } 

    public RulesException(string propertyName, string errorMessage, object onObject) : 
     this (new ErrorInfo[] { new ErrorInfo(propertyName, errorMessage, onObject) }) 
    { 

    } 
} 

Donc, avec cela dit, ma validation dans mon contrôleur ressemble plus à ce

public ActionResult MyAction() 
{ 
    try 
    { 
     //call validation here 
    } 
    catch (RulesException ex) 
    { 
     ModelState.AddModelStateErrors(ex); 
    } 

    return View(); 
} 

ModelState.AddModelStateErrors (ex); est une méthode d'extension que j'ai écrite. c'est très simple.

public static void AddModelStateErrors(this System.Web.Mvc.ModelStateDictionary modelState, RulesException exception) 
    { 
     foreach (ErrorInfo info in exception.Errors) 
     { 
      modelState.AddModelError(info.PropertyName, info.ErrorMessage); 
     } 
    } 

De cette façon, je peux toujours utiliser DI pour mes services/dépôts, et laissez-les vomissent une erreur quand mon modèle est invalide. Ensuite, je laisse la partie frontale - qu'il s'agisse d'une application MVC, d'un service Web ou d'une application Windows - décider quoi faire de ces erreurs.Je pense qu'injecter l'état du contrôleur/modèle/vue MVC dans le model/services/repositories/etc est une violation de la séparation de base entre les couches.

+0

Ça me va bien! Vous ne savez pas pourquoi votre classe RulesException est sériiazable? Pourriez-vous expliquer s'il vous plaît? – Haroon

+0

Je l'ai fait sérialisable afin que les exceptions de règles puissent être consommées et présentées dans une application appelant mon code via un service web. – Josh

Questions connexes