在MVC中,我可以创建一个可以获取依赖关系的模型验证器。我通常使用FluentValidation。例如,这允许我检查帐户注册是否未使用电子邮件地址(注意:这是一个简化的示例!):
public class RegisterModelValidator : AbstractValidator<RegisterModel> {
private readonly MyContext _context;
public RegisterModelValidator(MyContext context) {
_context = context;
}
public override ValidationResult Validate(ValidationContext<RegisterModel> context) {
var result = base.Validate(context);
if (context.Accounts.Any(acc => acc.Email == context.InstanceToValidate.Email)){
result.Errors.Add(new ValidationFailure("Email", "Email has been used"));
}
return result;
}
}
对于具有FluentValidation的Web API,不存在此类集成。此处有couple attempts,但都没有处理依赖注入方面,只能使用静态验证器。
这很困难的原因是由于MVC和Web API之间的ModelValidatorProvider和ModelValidator的实现不同。在MVC中,这些是按请求实例化的(因此注入上下文很容易)。在Web API中,它们是静态的,ModelValidatorProvider为每种类型维护一个ModelValidators缓存,以避免对每个请求进行不必要的反射查找。
我一直在尝试自己添加必要的集成,但已经stuck trying to obtain the Dependency Scope。相反,我想我会退后一步,询问是否还有其他问题的解决方案 - 如果有人提出了执行模型验证的解决方案,可以注入依赖项。
我不想在Controller中执行验证(我使用ValidationActionFilter来保持这个独立),这意味着我无法从构造函数注入控制器获得任何帮助。
答案 0 :(得分:6)
我能够使用GetDependencyScope()扩展方法注册然后从请求中访问Web API依赖项解析程序。这允许在执行验证过滤器时访问模型验证器。
如果这不能解决您的依赖注入问题,请随时澄清。
Web API配置(使用Unity作为IoC容器):
public static void Register(HttpConfiguration config)
{
config.DependencyResolver = new UnityDependencyResolver(
new UnityContainer()
.RegisterInstance<MyContext>(new MyContext())
.RegisterType<AccountValidator>()
.RegisterType<Controllers.AccountsController>()
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
验证操作过滤器:
public class ModelValidationFilterAttribute : ActionFilterAttribute
{
public ModelValidationFilterAttribute() : base()
{
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
var scope = actionContext.Request.GetDependencyScope();
if (scope != null)
{
var validator = scope.GetService(typeof(AccountValidator)) as AccountValidator;
// validate request using validator here...
}
base.OnActionExecuting(actionContext);
}
}
模型验证器:
public class AccountValidator : AbstractValidator<Account>
{
private readonly MyContext _context;
public AccountValidator(MyContext context) : base()
{
_context = context;
}
public override ValidationResult Validate(ValidationContext<Account> context)
{
var result = base.Validate(context);
var resource = context.InstanceToValidate;
if (_context.Accounts.Any(account => String.Equals(account.EmailAddress, resource.EmailAddress)))
{
result.Errors.Add(
new ValidationFailure("EmailAddress", String.Format("An account with an email address of '{0}' already exists.", resource.EmailAddress))
);
}
return result;
}
}
API控制器操作方法:
[HttpPost(), ModelValidationFilter()]
public HttpResponseMessage Post(Account account)
{
var scope = this.Request.GetDependencyScope();
if(scope != null)
{
var accountContext = scope.GetService(typeof(MyContext)) as MyContext;
accountContext.Accounts.Add(account);
}
return this.Request.CreateResponse(HttpStatusCode.Created);
}
模型(示例):
public class Account
{
public Account()
{
}
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public string EmailAddress
{
get;
set;
}
}
public class MyContext
{
public MyContext()
{
}
public List<Account> Accounts
{
get
{
return _accounts;
}
}
private readonly List<Account> _accounts = new List<Account>();
}
答案 1 :(得分:4)
我终于让它工作了,但这有点像一个小屋。如前所述,ModelValidatorProvider将保留所有Validator的Singleton实例,因此这完全不合适。相反,我正在使用过滤器来运行我自己的验证,正如Oppositional所建议的那样。此过滤器可以访问IDependencyScope
,并且可以整齐地实例化验证器。
在过滤器中,我浏览ActionArguments
,并通过验证传递它们。验证代码已从DefaultBodyModelValidator
的Web API运行时源中复制出来,已修改为在DependencyScope
内查找验证程序。
最后,要使用ValidationActionFilter
进行此操作,您需要ensure that your filters are executed in a specific order.
我已将我的解决方案打包在github上,并在nuget上提供了一个版本。
答案 2 :(得分:1)
我让DI在WebApi中使用Fluent Validators没有问题。我发现验证器被调用很多,而这些重型逻辑验证在模型验证器中没有任何地方。在我看来,模型验证器应该是轻量级检查数据的形状。 Email
看起来像是一封电子邮件,并且来电者提供了FirstName
,LastName
以及Mobile
或HomePhone
?
像这样的逻辑验证可以注册此电子邮件属于服务层,而不是控制器。我的实现也不共享隐式数据上下文,因为我认为这是一种反模式。
我认为当前的NuGet包具有MVC3依赖关系,因此我最终只是查看source directly并创建自己的NinjectFluentValidatorFactory
。
在App_Start/NinjectWebCommon.cs
我们有以下内容。
/// <summary>
/// Set up Fluent Validation for WebApi.
/// </summary>
private static void FluentValidationSetup(IKernel kernel)
{
var ninjectValidatorFactory
= new NinjectFluentValidatorFactory(kernel);
// Configure MVC
FluentValidation.Mvc.FluentValidationModelValidatorProvider.Configure(
provider => provider.ValidatorFactory = ninjectValidatorFactory);
// Configure WebApi
FluentValidation.WebApi.FluentValidationModelValidatorProvider.Configure(
System.Web.Http.GlobalConfiguration.Configuration,
provider => provider.ValidatorFactory = ninjectValidatorFactory);
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
}
我相信上面唯一需要的其他包装是:
<package id="FluentValidation" version="5.1.0.0" targetFramework="net451" />
<package id="FluentValidation.MVC5" version="5.1.0.0" targetFramework="net451" />
<package id="FluentValidation.WebApi" version="5.1.0.0" targetFramework="net451" />
<package id="Ninject" version="3.2.0.0" targetFramework="net451" />
<package id="Ninject.MVC3" version="3.2.0.0" targetFramework="net451" />
<package id="Ninject.Web.Common" version="3.2.0.0" targetFramework="net451" />
答案 3 :(得分:0)
我花了很多时间试图找到一个很好的方法来解决WebApi ModelValidatorProvider将验证器存储为单例的问题。我不想用验证过滤器来标记内容,因此我最终在验证器中注入了IKernel并使用它来获取上下文。
public class RequestValidator : AbstractValidator<RequestViewModel>{
public readonly IDbContext context;
public RequestValidator(IKernel kernel) {
this.context = kernel.Get<IDbContext>();
RuleFor(r => r.Data).SetValidator(new DataMustHaveValidPartner(kernel)).When(r => r.RequestableKey == "join");
}
}
即使验证器存储为单例,这似乎也能正常工作。如果您还希望能够使用上下文调用它,则可以创建第二个构造函数,该构造函数使用IDbContext
并使用IKernel
使IDbContext
构造函数传递kernel.Get<IDbContext>()
< / p>
答案 4 :(得分:0)
当然不建议使用此类,因为该类是内部的,但您可以在WebApi配置中删除IModelValidatorCache服务。
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Services.Clear(Type.GetType("System.Web.Http.Validation.IModelValidatorCache, System.Web.Http"));
}
}
答案 5 :(得分:-1)
FluentValidation已经支持WebApi很长一段时间了(不确定你的问题是否在此之前确定):https://fluentvalidation.codeplex.com/discussions/533373
从帖子中引用:
{
GlobalConfiguration.Configuration.Services.Add(typeof(System.Web.Http.Validation.ModelValidatorProvider),
new WebApiFluentValidationModelValidatorProvider()
{
AddImplicitRequiredValidator = false //we need this otherwise it invalidates all not passed fields(through json). btw do it if you need
});
FluentValidation.ValidatorOptions.ResourceProviderType = typeof(FluentValidationMessages); // if you have any related resource file (resx)
FluentValidation.ValidatorOptions.CascadeMode = FluentValidation.CascadeMode.Continue; //if you need!
我一直在WebApi2项目中使用它而没有任何问题。