Web API中的依赖项注入validation
在MVC中,我可以创建一个可以使用依赖关系的模型validation器。 我通常使用FluentValidation。 例如,这允许我检查帐户注册是否未使用电子邮件地址(注意:这是一个简化的示例!):
public class RegisterModelValidator : AbstractValidator { private readonly MyContext _context; public RegisterModelValidator(MyContext context) { _context = context; } public override ValidationResult Validate(ValidationContext 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不存在此类集成。 已经有过几次 尝试 ,但都没有处理dependency injection方面,只能使用静态validation器。
这很困难的原因是由于MVC和Web API之间的ModelValidatorProvider和ModelValidator的实现不同。 在MVC中,这些是按请求实例化的(因此注入上下文很容易)。 在Web API中,它们是静态的,ModelValidatorProvider为每种类型维护ModelValidators的缓存,以避免对每个请求进行不必要的reflection查找。
我一直试图自己添加必要的集成,但一直试图获得依赖范围 。 相反,我想我会退后一步,询问是否还有其他问题的解决方案 – 如果有人提出了执行模型validation的解决方案,可以注入依赖关系。
我不想在Controller中执行validation(我使用ValidationActionFilter来保持这一点),这意味着我无法从构造函数注入控制器获得任何帮助。
我能够使用GetDependencyScope()扩展方法注册然后从请求访问Web API依赖项解析器。 这允许在执行validationfilter时访问模型validation器。
如果这不能解决您的dependency injection问题,请随时澄清。
Web API配置(使用Unity作为IoC容器):
public static void Register(HttpConfiguration config) { config.DependencyResolver = new UnityDependencyResolver( new UnityContainer() .RegisterInstance(new MyContext()) .RegisterType() .RegisterType() ); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); }
validation操作filter:
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); } }
模型validation器:
public class AccountValidator : AbstractValidator { private readonly MyContext _context; public AccountValidator(MyContext context) : base() { _context = context; } public override ValidationResult Validate(ValidationContext 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 Accounts { get { return _accounts; } } private readonly List _accounts = new List (); }
我终于有了这个工作,但这有点像一个小屋。 如前所述,ModelValidatorProvider将保留所有Validator的Singleton实例,因此这完全不合适。 相反,我正在使用filter来运行我自己的validation,正如Oppositional所建议的那样。 此filter可以访问IDependencyScope
并可以整齐地实例化validation器。
在Filter中,我查看了ActionArguments
,并通过validation传递它们。 validation代码从DefaultBodyModelValidator
的Web API运行时源复制DefaultBodyModelValidator
,经过修改以在DependencyScope
查找Validator。
最后,要使用ValidationActionFilter
进行此操作,您需要确保按特定顺序执行filter。
我已经在github上打包了我的解决方案,在nuget上提供了一个版本。
我在网络中使用DI与Fluent Validators一起工作没有任何问题。 我发现validation器被调用很多,而这些重型逻辑validation在模型validation器中没有位置。 在我看来,模型validation器应该是轻量级检查数据的形状。 Email
看起来像电子邮件,并且呼叫者是否提供了FirstName
, LastName
以及Mobile
或HomePhone
?
像是否可以注册此电子邮件的逻辑validation属于服务层,而不是控制器。 我的实现也不共享隐式数据上下文,因为我认为这是一种反模式。
我认为当前的NuGet包具有MVC3依赖关系,所以我最终只是直接查看源代码并创建自己的NinjectFluentValidatorFactory
。
在App_Start/NinjectWebCommon.cs
我们有以下内容。
/// /// Set up Fluent Validation for WebApi. /// 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; }
我相信上面唯一需要的其他包装是:
我花了很多时间试图找到一个好的方法,即WebApi ModelValidatorProvider将validation器存储为单例。 我不想用validationfilter来标记内容,因此我最终在validation器中注入了IKernel并使用它来获取上下文。
public class RequestValidator : AbstractValidator{ public readonly IDbContext context; public RequestValidator(IKernel kernel) { this.context = kernel.Get(); RuleFor(r => r.Data).SetValidator(new DataMustHaveValidPartner(kernel)).When(r => r.RequestableKey == "join"); } }
即使validation器存储为单例,这似乎也可以工作。 如果您还希望能够使用上下文调用它,则可以创建第二个构造函数,该构造IDbContext
使用kernel.Get
并使IKernel
构造IDbContext
使用kernel.Get
传递IDbContext
这当然不建议使用,因为类是内部的,但您可以在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")); } }
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项目中使用它没有任何问题。
- 无法在ASP.NET WebApi控制器中读取Request.Content
- 在ASP.Net Web API中的日志记录DelegatingHandler中读取HttpRequestMessage.Content时丢失
- MVC Web API绑定模型到派生类
- 如何回答请求但继续在WebApi中处理代码
- 使用WebAPI流式传输大型文件(超过IIS 2GB)
- ASP.NET 5,MVC6,WebAPI – > ModelState.IsValid始终返回true
- 如何从ASP.NET Web API读取XML?
- 升级到Web API 2后,HttpApplication.Application_Start未触发
- 为什么RestSharp对两个日期进行不同的反序列化?