- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在我的 WebAPI 项目中,我正在使用 FluentValidation。我通过添加在全局范围内启用它FluentValidationModelValidatorProvider.Configure(config);
在 Startup.cs 中
我添加了自定义 ActionFolterAttribute
,它在我的方法中使用之前会更改模型,但在测试之后我发现我的执行顺序不正确。
我希望在 FluentVatiodation 验证我的模型之前对其进行更改,但现在它在 FluentVatiodation 验证我的模型之后更新。
我需要它才能访问 FluentVatiodation 验证数据中的一些路由数据
下面是我的自定义属性:
public class UpdateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
if (actionContext.ActionArguments.Any())
{
var args = actionContext.ActionArguments;
var pId = args["productId"] as int?;
var model = args["newAccount"] as TestBindingModel;
if (pId.HasValue && model != null)
{
model.Id = pId.Value;
}
}
base.OnActionExecuting(actionContext);
}
}
我的带有验证器的模型:
[Validator(typeof(TestBindingModelValidator))]
public class TestBindingModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public class TestBindingModelValidator : AbstractValidator<TestBindingModel>
{
public TestBindingModelValidator()
{
RuleFor(u => u.Id)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Id is required")
.Must(BetweenOneAndTwo).WithMessage("Id is bad");
RuleFor(u => u.Name)
.Cascade(CascadeMode.StopOnFirstFailure)
.NotEmpty().WithMessage("Name is required");
}
private bool BetweenOneAndTwo(TestBindingModel createAccountBindingModel, int id, PropertyValidatorContext context)
{
return id > 1;
}
}
还有我的方法:
[AllowAnonymous]
[Route("create/{productId:int?}")]
[HttpPost]
[UpdateModelAttribute]
public async Task<IHttpActionResult> CreateAccount(TestBindingModel newAccount, int productId=100)
{
if (!ModelState.IsValid)
{
return BadRequest("Invalid data");
}
Debug.WriteLine("{0} {1}", newAccount.Id, newAccount.Name);
await Task.CompletedTask;
return Ok("Works fine!");
}
我已经使用 PostMan 将 POST 发送到 URL http://localhost:63564/test/create/20
并使用数据进行了检查:
Id:1
Name:Test
内部验证器 Id 的值为 1,但在我的方法主体中值为 20。
我想更改该顺序并在我的验证器中使用更新后的值。
这可以改变吗?
这里讨论了类似的事情:Access route data in FluentValidation for WebApi 2并且我基于问题作者评论的上述解决方案。
最佳答案
是的,它可以更改,但是您需要将通用过滤器提供程序替换为强制执行已定义顺序的过滤器提供程序。
webApiConfiguration.Services.Replace(typeof(System.Web.Http.Filters.IFilterProvider), new OrderedFilterProvider());
您可以按照您希望它们触发的顺序添加过滤器,如下所示:
webApiConfiguration.Filters.Add(new UpdateModelAttribute());
webApiConfiguration.Filters.Add(new ValidationActionFilter());
或者设置 IOrderedFilterAttribute 公开的 Order 属性。如果您想通过配置/依赖项注入(inject)或编译时未知的其他一些因素来控制顺序,您可能希望使用此方法。
OrderedFilterProvider.cs
/// <summary>
/// Combines Action Filters from multiple sources
/// </summary>
public class OrderedFilterProvider : IFilterProvider
{
private List<IFilterProvider> _filterProviders;
/// <summary>
/// Constructor using default filter providers
/// </summary>
public OrderedFilterProvider()
{
_filterProviders = new List<IFilterProvider>();
_filterProviders.Add(new ConfigurationFilterProvider());
_filterProviders.Add(new ActionDescriptorFilterProvider());
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="innerProviders">The inner providers.</param>
public OrderedFilterProvider(IEnumerable<IFilterProvider> innerProviders)
{
_filterProviders = innerProviders.ToList();
}
/// <summary>
/// Returns all appropriate Filters for the specified action, sorted by their Order property if they have one
/// </summary>
public IEnumerable<FilterInfo> GetFilters(HttpConfiguration configuration, HttpActionDescriptor actionDescriptor)
{
if (configuration == null) { throw new ArgumentNullException("configuration"); }
if (actionDescriptor == null) { throw new ArgumentNullException("actionDescriptor"); }
List<OrderedFilterInfo> filters = new List<OrderedFilterInfo>();
foreach (IFilterProvider fp in _filterProviders)
{
filters.AddRange(
fp.GetFilters(configuration, actionDescriptor)
.Select(fi => new OrderedFilterInfo(fi.Instance, fi.Scope)));
}
var orderedFilters = filters.OrderBy(i => i).Select(i => i.ConvertToFilterInfo());
return orderedFilters;
}
}
要使用它,您需要一些支持类。
OrderedFilterInfo.cs
/// <summary>
/// Our version of FilterInfo, with the ability to sort by an Order attribute. This cannot simply inherit from
/// FilterInfo in the Web API class because it's sealed :(
/// </summary>
public class OrderedFilterInfo : IComparable
{
public OrderedFilterInfo(IFilter instance, FilterScope scope)
{
if (instance == null) { throw new ArgumentNullException("instance"); }
Instance = instance;
Scope = scope;
}
/// <summary>
/// Filter this instance is about
/// </summary>
public IFilter Instance { get; private set; }
/// <summary>
/// Scope of this filter
/// </summary>
public FilterScope Scope { get; private set; }
/// <summary>
/// Allows controlled ordering of filters
/// </summary>
public int CompareTo(object obj)
{
if (obj is OrderedFilterInfo)
{
var otherfilterInfo = obj as OrderedFilterInfo;
// Global filters should be executed before Controller and Action Filters. We don't strictly have to
// do this, since it's done again in the framework, but it's a little more consistent for testing!
if (this.Scope == FilterScope.Global && otherfilterInfo.Scope != FilterScope.Global)
{
return -10;
}
else if (this.Scope != FilterScope.Global && otherfilterInfo.Scope == FilterScope.Global)
{
return 10;
}
IOrderedFilterAttribute thisAttribute = this.Instance as IOrderedFilterAttribute;
IOrderedFilterAttribute otherAttribute = otherfilterInfo.Instance as IOrderedFilterAttribute;
IFilter thisNonOrderedAttribute = this.Instance as IFilter;
IFilter otherNonOrderedAttribute = otherfilterInfo.Instance as IFilter;
if (thisAttribute != null && otherAttribute != null)
{
int value = thisAttribute.Order.CompareTo(otherAttribute.Order);
if (value == 0)
{
// If they both have the same order, sort by name instead
value = thisAttribute.GetType().FullName.CompareTo(otherAttribute.GetType().FullName);
}
return value;
}
else if (thisNonOrderedAttribute != null && otherAttribute != null)
{
return 1;
}
else if (thisAttribute != null && otherNonOrderedAttribute != null)
{
return -1;
}
{
return thisNonOrderedAttribute.GetType().FullName.CompareTo(otherNonOrderedAttribute.GetType().FullName);
}
}
else
{
throw new ArgumentException("Object is of the wrong type");
}
}
/// <summary>
/// Converts this to a FilterInfo (because FilterInfo is sealed, and we can't extend it. /sigh
/// </summary>
/// <returns></returns>
public FilterInfo ConvertToFilterInfo()
{
return new FilterInfo(Instance, Scope);
}
}
IOrderedFilterAttribute.cs:
/// <summary>
/// Allows ordering of filter attributes
/// </summary>
public interface IOrderedFilterAttribute
{
/// <summary>
/// Order of execution for this filter
/// </summary>
int Order { get; set; }
}
BaseActionFilterAttribute.cs
public abstract class BaseActionFilterAttribute : ActionFilterAttribute, IOrderedFilterAttribute
{
/// <summary>
/// Order of execution for this filter
/// </summary>
public int Order { get; set; }
public BaseActionFilterAttribute()
{
Order = 0;
}
public BaseActionFilterAttribute(int order)
{
Order = order;
}
}
FluentValidationActionFilter.cs
/// <summary>
/// A Filter which can be applied to Web API controllers or actions which runs any FluentValidation Validators
/// registered in the DependencyResolver to be run. It's not currently possible to perform this validation in the
/// standard Web API validation location, since this doesn't provide any way of instantiating Validators on a
/// per-request basis, preventing injection of Unit of Work or DbContexts, for example. ///
/// </summary>
public class FluentValidationActionFilter : BaseActionFilterAttribute
{
private static readonly List<HttpMethod> AllowedHttpMethods = new List<HttpMethod> { HttpMethod.Post, HttpMethod.Put, HttpMethod.Delete };
/// <summary>
/// Constructor
/// </summary>
/// <param name="order">Order to run this filter</param>
public FluentValidationActionFilter(int order = 1)
: base(order)
{ }
/// <summary>
/// Pick out validation errors and turn these into a suitable exception structure
/// </summary>
/// <param name="actionContext">Action Context</param>
public override void OnActionExecuting(HttpActionContext actionContext)
{
ModelStateDictionary modelState = actionContext.ModelState;
// Only perform the FluentValidation if we've not already failed validation earlier on
if (modelState.IsValid && AllowedHttpMethods.Contains(actionContext.Request.Method))
{
IDependencyScope scope = actionContext.Request.GetDependencyScope();
var mvp = scope.GetService(typeof(IFluentValidatorProvider)) as IFluentValidatorProvider;
if (mvp != null)
{
ModelMetadataProvider metadataProvider = actionContext.GetMetadataProvider();
foreach (KeyValuePair<string, object> argument in actionContext.ActionArguments)
{
if (argument.Value != null && !argument.Value.GetType().IsSimpleType())
{
ModelMetadata metadata = metadataProvider.GetMetadataForType(
() => argument.Value,
argument.Value.GetType()
);
var validationContext = new InternalValidationContext
{
MetadataProvider = metadataProvider,
ActionContext = actionContext,
ModelState = actionContext.ModelState,
Visited = new HashSet<object>(),
KeyBuilders = new Stack<IKeyBuilder>(),
RootPrefix = String.Empty,
Provider = mvp,
Scope = scope
};
ValidateNodeAndChildren(metadata, validationContext, null);
}
}
}
}
}
/// <summary>
/// Validates a single node (not including children)
/// </summary>
/// <param name="metadata">Model Metadata</param>
/// <param name="validationContext">Validation Context</param>
/// <param name="container">The container.</param>
/// <returns>True if validation passes successfully</returns>
private static bool ShallowValidate(ModelMetadata metadata, InternalValidationContext validationContext, object container)
{
bool isValid = true;
// Use the DependencyResolver to find any validators appropriate for this type
IEnumerable<IValidator> validators = validationContext.Provider.GetValidators(metadata.ModelType, validationContext.Scope);
foreach (IValidator validator in validators)
{
IValidatorSelector selector = new DefaultValidatorSelector();
var context = new ValidationContext(metadata.Model, new PropertyChain(), selector);
ValidationResult result = validator.Validate(context);
foreach (var error in result.Errors)
{
if (!validationContext.ModelState.ContainsKey(error.PropertyName))
{
validationContext.ModelState.Add(error.PropertyName, new ModelState
{
Value = new ValueProviderResult(error.AttemptedValue, error.AttemptedValue?.ToString(), CultureInfo.CurrentCulture)
});
}
validationContext.ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
isValid = false;
}
}
return isValid;
}
#region Copied from DefaultBodyModelValidator in Web API Source
private bool ValidateElements(IEnumerable model, InternalValidationContext validationContext)
{
bool isValid = true;
Type elementType = GetElementType(model.GetType());
ModelMetadata elementMetadata = validationContext.MetadataProvider.GetMetadataForType(null, elementType);
var elementScope = new ElementScope { Index = 0 };
validationContext.KeyBuilders.Push(elementScope);
foreach (object element in model)
{
elementMetadata.Model = element;
if (!ValidateNodeAndChildren(elementMetadata, validationContext, model))
{
isValid = false;
}
elementScope.Index++;
}
validationContext.KeyBuilders.Pop();
return isValid;
}
private bool ValidateNodeAndChildren(ModelMetadata metadata, InternalValidationContext validationContext, object container)
{
bool isValid = true;
object model = metadata.Model;
// Optimization: we don't need to recursively traverse the graph for null and primitive types
if (model != null && model.GetType().IsSimpleType())
{
return ShallowValidate(metadata, validationContext, container);
}
// Check to avoid infinite recursion. This can happen with cycles in an object graph.
if (validationContext.Visited.Contains(model))
{
return true;
}
validationContext.Visited.Add(model);
// Validate the children first - depth-first traversal
var enumerableModel = model as IEnumerable;
if (enumerableModel == null)
{
isValid = ValidateProperties(metadata, validationContext);
}
else
{
isValid = ValidateElements(enumerableModel, validationContext);
}
if (isValid && metadata.Model != null)
{
// Don't bother to validate this node if children failed.
isValid = ShallowValidate(metadata, validationContext, container);
}
// Pop the object so that it can be validated again in a different path
validationContext.Visited.Remove(model);
return isValid;
}
private bool ValidateProperties(ModelMetadata metadata, InternalValidationContext validationContext)
{
bool isValid = true;
var propertyScope = new PropertyScope();
validationContext.KeyBuilders.Push(propertyScope);
foreach (ModelMetadata childMetadata in validationContext.MetadataProvider.GetMetadataForProperties(
metadata.Model, GetRealModelType(metadata)))
{
propertyScope.PropertyName = childMetadata.PropertyName;
if (!ValidateNodeAndChildren(childMetadata, validationContext, metadata.Model))
{
isValid = false;
}
}
validationContext.KeyBuilders.Pop();
return isValid;
}
#endregion Copied from DefaultBodyModelValidator in Web API Source
#region Inaccessible Helper Methods from the Web API source needed by the other code here
private interface IKeyBuilder
{
string AppendTo(string prefix);
}
private static string CreateIndexModelName(string parentName, int index) => CreateIndexModelName(parentName, index.ToString(CultureInfo.InvariantCulture));
private static string CreateIndexModelName(string parentName, string index) => (parentName.Length == 0) ? $"[{index}]" : $"{parentName}[{index}]";
private static string CreatePropertyModelName(string prefix, string propertyName)
{
if (String.IsNullOrEmpty(prefix))
{
return propertyName ?? String.Empty;
}
else if (String.IsNullOrEmpty(propertyName))
{
return prefix ?? String.Empty;
}
else
{
return prefix + "." + propertyName;
}
}
private static Type GetElementType(Type type)
{
Contract.Assert(typeof(IEnumerable).IsAssignableFrom(type));
if (type.IsArray)
{
return type.GetElementType();
}
foreach (Type implementedInterface in type.GetInterfaces())
{
if (implementedInterface.IsGenericType && implementedInterface.GetGenericTypeDefinition() == typeof(IEnumerable<>))
{
return implementedInterface.GetGenericArguments()[0];
}
}
return typeof(object);
}
private Type GetRealModelType(ModelMetadata metadata)
{
Type realModelType = metadata.ModelType;
// Don't call GetType() if the model is Nullable<T>, because it will
// turn Nullable<T> into T for non-null values
if (metadata.Model != null && !metadata.ModelType.IsNullableValueType())
{
realModelType = metadata.Model.GetType();
}
return realModelType;
}
private class ElementScope : IKeyBuilder
{
public int Index { get; set; }
public string AppendTo(string prefix) => CreateIndexModelName(prefix, Index);
}
private class PropertyScope : IKeyBuilder
{
public string PropertyName { get; set; }
public string AppendTo(string prefix) => CreatePropertyModelName(prefix, PropertyName);
}
#endregion Inaccessible Helper Methods from the Web API source needed by the other code here
private class InternalValidationContext
{
public HttpActionContext ActionContext { get; set; }
public Stack<IKeyBuilder> KeyBuilders { get; set; }
public ModelMetadataProvider MetadataProvider { get; set; }
public ModelStateDictionary ModelState { get; set; }
public IFluentValidatorProvider Provider { get; set; }
public string RootPrefix { get; set; }
public IDependencyScope Scope { get; set; }
public HashSet<object> Visited { get; set; }
}
ValidationActionFilter.cs - 这实际上返回一个错误模型:
public class ValidationActionFilter : BaseActionFilterAttribute
{
// This must run AFTER the FluentValidation filter, which runs as 0
public ValidationActionFilter() : base(1000) { }
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (modelState.IsValid) return;
var errors = new ErrorModel();
foreach (KeyValuePair<string, ModelState> item in actionContext.ModelState)
{
errors.ModelErrors.AddRange(item.Value.Errors.Select(e => new ModelPropertyError
{
PropertyName = item.Key,
ErrorMessage = e.ErrorMessage
}));
}
actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.BadRequest, errors);
}
}
IFluentValidatorProvider.cs
/// <summary>
/// Provides FluentValidation validators for a type
/// </summary>
public interface IFluentValidatorProvider
{
/// <summary>
/// Provides any FluentValidation Validators appropriate for validating the specified type. These will have
/// been created within the specified Dependency Scope
/// </summary>
/// <param name="type">Model type to find validators for</param>
/// <param name="scope">Scope to create validators from</param>
/// <returns></returns>
IEnumerable<IValidator> GetValidators(Type type, IDependencyScope scope);
}
关于c# - FluentValidation 和 ActionFilterAttribute - 在验证之前更新模型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40932102/
如果另一个属性“Found”为真,我正在尝试使用 FluentValidation 来验证属性“Username”。 Object Contains: string Username bo
如果另一个属性“Found”为真,我正在尝试使用 FluentValidation 来验证属性“Username”。 Object Contains: string Username bo
我有一个包含需要验证的集合的类。集合上的泛型采用接口(interface),可以将不同的类型添加到集合中。 创建支持多态性的 FluentValidation 验证器的最简洁路径是什么? public
我已经开始使用 FluentValidation在 WPF 项目中,到目前为止,我以一种简单的方式使用它,检查字段是否已填充或少于 n 个字符。 现在我必须检查插入的值(这是一个字符串......该死
如何使用流畅的验证来验证整数列表? 我的模型有: public List WindowGlassItems { get; set; } 模型验证器有 RuleFor(x => x.WindowGla
如何使用流畅的验证来验证整数列表? 我的模型有: public List WindowGlassItems { get; set; } 模型验证器有 RuleFor(x => x.WindowGla
我在不止一个程序集中有 FluentValidators。在 FluentValidation 的早期版本中,我可以这样注册: services.AddMvc() .AddFluentValida
试图找出让 NHibernate ISession 成为流畅的验证抽象验证器的最佳方法。问题是 ISession 是根据 Web 请求注册的,而验证器出于性能原因根据 fluent 的最佳实践注册为单
我已经尝试在我的 ASP.NET MVC4 项目中设置 Ninject.Web.Mvc.FluentValidation如下图: var ninjectValidatorFactory = new N
我正在使用 FluentValidation.AspNetcore 8.2.2,并且有一个对象模型,其中包含相同类型的子项列表。我想使用流畅的验证来验证对象。尝试为子对象设置验证器时,我遇到堆栈溢出异
我想试试 .NET FluentValidation用于即将进行的项目的表单输入验证的库。在查看文档时,我发现了这个: 性能说明 由于 RuleFor 定义中的表达式树编译和解析,验证器的实例化是一个
我只想验证在我的模型上发生更改的属性,不幸的是,默认情况下流畅的验证似乎会验证调用 Validator.Validate(instanceToValidate) 时具有规则的每个属性 我已经尝试将 P
我有课: Sponsored { int Order }; 我收集了它: IEnumerable sponsored; 我想检查 Order 对于这个集合是否是唯一的。 我可以通过 FluentVal
我正在使用图书馆 "FluentValidation.AspNetCore": "6.4.0-beta3"在 .netcore WebApi在一个项目中。您可以在下面看到项目结构。如果我放置 Curr
Reading the documentation在 .NET Core 中,您似乎可以使用以下方法进行隐式子属性验证: services.AddMvc().AddFluentValidation(f
我有这个验证器: public class InputValidator : AbstractValidator { public InputValidator() {
我正在 .NET Core 中使用 FluentValidator。大多数情况下,一切都运行良好,但我注意到在处理复杂类型时,FluentValidator 会显示完整的属性名称。例如,假设我有一个名
我正在努力为一个类实现一个验证器,其中应该只设置一个属性。 假设我们有以下类: public class SomeClass { public DateTime SomeDate {get;
我一直在 ASP.NET MVC 3 中的 View 模型上使用 FluentValidation,它工作得很棒! 我现在想将它用作我的服务层内域对象的验证引擎。 你能用它做复杂的验证方案吗? 我正在
我想像这样使用 FluentValidation: public class CustomValidator : AbstractValidator { public CustomValidato
我是一名优秀的程序员,十分优秀!