c# - 将 ASP.NET MVC Controller 属性注入(inject)服务层依赖项?

标签 c# asp.net-mvc dependency-injection castle-windsor

我使用的方法与此 ASP.NET MVC tutorial 中的方法类似将 Controller 的 ModelState 集合的包装器传递到验证类中,以便 Controller 可以访问错误信息。

这里有一个例子:

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

使用 Castle Windsor IoC/DI 的容器,如何创建 IProductService?通常,我会:

MvcApplication.IocContainer.Resolve<IProductService>()

但这无法将 Controller 的 ModelState 属性的值注入(inject)到 ProductValidator 的构造函数中。我可以使用构造函数参数将它连接起来,但这看起来真的很难看。

最佳答案

我假设您希望传入的模型状态自动将任何错误注入(inject)您的模型?恕我直言,ModelState 应该保持原样,并且您将验证错误带到它那里。下面以我处理错误的方式为例。我并不是说这是最好的方法或唯一的方法,但这是一种方法,您的验证层不必知道谁或什么消耗了验证错误。

首先,在我的 poco 中,我使用 System.ComponentModel.DataAnnotations 作为验证规则。例如,这是我的帐户类。

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

因为我希望能够自己启动验证(在 MVC 之外自行执行),所以我必须实现自己的验证器。

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

这里是我传回的ValidationResult

[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 是一个非常基本的类,包含有关我的错误的信息

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

为了将此验证与我的 poco 类一起很好地和整洁地包装起来,我继承了一个基类。为了使继承的子类必须告诉基类它的类型,使其成为通用的验证。感觉是圆形的,但确实有效。

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

最后,如您所见,我的验证抛出了 RulesException。这个类是所有错误的包装器。

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

    }
}

所以,话虽如此,我在 Controller 中的验证看起来更像这样

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

   return View();
}

ModelState.AddModelStateErrors(前);是我写的扩展方法。这很简单。

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

这样,我仍然可以将 DI 用于我的服务/存储库,并让它们在我的模型无效时抛出错误。然后我让前端——无论是 MVC 应用程序、Web 服务还是 Windows 应用程序——决定如何处理这些错误。

我觉得将 MVC Controller /模型/ View 状态注入(inject)回模型/服务/存储库/等违反了层之间的基本分离。

关于c# - 将 ASP.NET MVC Controller 属性注入(inject)服务层依赖项?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3873530/

相关文章:

c# - 仅针对发布版本从项目文件中删除注释

c# - 在 ASP.NET 的一对多关系中,外键为 NULL。如何将其添加到 Controller 中?

c# - 在 asp.net mvc 应用程序中将评论保存到数据库时如何维护评论的格式?

javascript - AngularJS:添加依赖项(内联数组与注入(inject))

外部 dll 中的 c#/wpf OpenMP

c# - 使用 XAML 和 C# 为 Windows Phone 8 应用构建自定义进度条

ASP.NET 自动部署到远程 ftp 服务器

javascript - MVC FileUpload 文件大小客户端验证

c# - 通过依赖注入(inject)使用异步 Controller 时 dbContext 的生命周期

ember.js - Ember Simple Auth - 将当前用户注入(inject)每条路由