开发者

Inject ASP.NET MVC Controller property into service layer dependency?

开发者 https://www.devze.com 2023-01-19 02:58 出处:网络
I am using an approach similar to the one in this ASP.NET MVC tutorial where you pass a wrapper around a controller\'s ModelState collection into a validation class so that the controller can access e

I am using an approach similar to the one in this ASP.NET MVC tutorial where you pass a wrapper around a controller's ModelState collection into a validation class so that the controller can access error information.

Here is a cooked up example:

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

Using the Castle Windsor container for IoC/DI, how do I create the IProductService? Typically, I would have:

MvcApplication.IocContainer.Resolve<IProductService>()

but this isn't able to inject the value of the Controller's ModelState property into th开发者_运维知识库e constructor for ProductValidator. I could possibly wire this up using constructor parameters, but that seems really ugly.


I'm assuming you want the modelstate passed in to auto inject any errors into your model? IMHO, ModelState should stay where it is, and you bring the validation errors to it. Here's how I handle errors as an example. I'm not saying this is the best way or the only way, but it is one way where your validation layer doesn't have to have knowledge of who or what consumes validation errors.

First, in my poco, I use System.ComponentModel.DataAnnotations for validation rules. Here is my account class, for example.

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

Because I want to be able to initiate validation myself (outside of MVC doing it on it's own), I had to implement my own validator.

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

Here is the ValidationResult I pass back

[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 is a very basic class with information about my error

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

in order to wrap this validation up all nice and neat with my poco classes, I inherit from a base class. For validation to make it generic where the inherited child has to tell the base class it's type. It feels circular, but it works.

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

And finally, as you can see, my validation throws a RulesException. This class is a wrapper for all the errors.

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

    }
}

So, with that said, my validation in my controller looks more like this

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

   return View();
}

ModelState.AddModelStateErrors(ex); is an extension method that I wrote. it is very 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);
        }
    }

This way, I can still use DI for my services/repositories, and let them throw up an error when my model is invalid. Then I let the front end - whether that is an MVC app, web service, or windows app - decide what to do with those errors.

I feel that injecting MVC controller/model/view state back into the model/services/repositories/etc is a violation of the basic separation between the layers.

0

精彩评论

暂无评论...
验证码 换一张
取 消