2010-10-06 14 views
9

Ich verwende einen Ansatz ähnlich dem in diesem ASP.NET MVC tutorial, wo Sie einen Wrapper um die ModelState Sammlung eines Controllers in eine Validierungsklasse übergeben, damit der Controller auf Fehlerinformationen zugreifen kann. HierASP.NET MVC Controller-Eigenschaft in Service-Layer-Abhängigkeit einspeisen?

ist ein gekochtes Beispiel:

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) { } 
} 

Mit dem Castle Windsor Behälter für IoC/DI, wie erstelle mich die IProductService? Typischerweise würde, ich habe:

MvcApplication.IocContainer.Resolve<IProductService>() 

aber das ist nicht in der Lage, den Wert der Controller-ModelState Eigenschaft in den Konstruktor für ProductValidator zu injizieren. Ich könnte das unter Verwendung von Konstruktorparametern verdrahten, aber das scheint wirklich hässlich zu sein.

+0

Haben Sie ausgecheckt: http://stackoverflow.com/questions/2077055/ioc-on-ivalidationdictionary-with-castle-windsor – bzarah

+0

Das ist eine ziemlich ähnliche Frage: In meinem Fall versuche ich es nicht einmal um einen Validator zu instantiieren-- Ich möchte, dass der Container es auflöst. In jedem Fall hat die Frage, die Sie verlinkt haben, keine Antworten für mich. Ich lasse das stehen und hoffe auf zusätzliche Erkenntnisse. – user403830

+0

Ich hatte gehofft, dass es einen Weg gibt, den aktuellen ControllerContext statisch zu erhalten, genauso wie man den aktuellen HttpContext mit HttpContext.Current bekommen kann. Leider konnte ich keinen finden. Eine andere Idee wäre, eine zusätzliche Methode für IProductValidator zu haben, die einen "ModelStateWrapper" akzeptiert und die Validierungsfehler dorthin kopiert. Nicht so nett wie Dependency Injection, aber es sollte ziemlich einfach funktionieren. – PatrickSteele

Antwort

2

Ich nehme an, Sie möchten, dass das modestate übergeben wird, um automatisch Fehler in Ihr Modell zu injizieren? IMHO, ModelState sollte bleiben, wo es ist, und Sie bringen die Validierungsfehler dazu. So behandle ich zum Beispiel Fehler. Ich sage nicht, dass dies der beste Weg oder der einzige Weg ist, aber es ist ein Weg, auf dem Ihre Validierungsschicht nicht wissen muss, wer oder was Validierungsfehler verbraucht.

Zuerst, in meinem poco, verwende ich System.ComponentModel.DataAnnotations für Validierungsregeln. Hier ist zum Beispiel meine Account-Klasse.

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

Weil ich die Validierung in der Lage sein, mich zu initiieren (außerhalb von MVC es auf seine eigene zu tun), hatte ich meine eigenen Validator zu implementieren.

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

Hier wird die Validation Ich gehe zurück

[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 eine sehr einfache Klasse mit Informationen über meine Fehler

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

um diese Validierung einpacken alle nett und ordentlich Mit meinen Poco-Klassen erben wir von einer Basisklasse. Für die Validierung, um es generisch zu machen, wo das geerbte Kind der Basisklasse seinen Typ mitteilen muss. Es fühlt sich kreisförmig an, aber es funktioniert.

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

Und schließlich, wie Sie sehen können, löst meine Validierung eine RulesException aus. Diese Klasse ist ein Wrapper für alle Fehler.

[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) }) 
    { 

    } 
} 

Also, mit dieser sagte, meine Validierung in meinem Controller sieht eher wie dieses

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

    return View(); 
} 

ModelState.AddModelStateErrors (ex); ist eine Erweiterungsmethode, die ich geschrieben habe. es ist sehr einfach.

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

Auf diese Weise kann ich DI noch für meine Dienste/Repositories, und lassen Sie sie einen Fehler aus, wenn mein Modell ungültig ist. Dann lasse ich das Frontend - egal ob MVC-App, Web-Service oder Windows-App - entscheiden, was mit diesen Fehlern geschehen soll.

Ich denke, MVC Controller/Modell/Ansicht Zustand in das Modell/Services/Repositories/etc zu injizieren ist eine Verletzung der grundlegenden Trennung zwischen den Schichten.

+0

Sieht gut aus für mich! Nicht sicher, warum Ihre RegulationException-Klasse serialisiert werden kann? Könnten Sie bitte erklären? – Haroon

+0

Ich habe es serialisierbar gemacht, damit Regelausnahmen konsumiert und in einer App dargestellt werden können, die meinen Code über einen Webservice aufruft. – Josh