2012年12月18日更新
2012年12月18日更新
您是否考虑过使用modelValidatorProvider创建模型验证器,而不是使用验证属性?这样您就不依赖于ValidationAttribute,但可以创建自己的验证实现(这将与现有的DataAnnotations验证一起使用)。
http://msdn.microsoft.com/en-us/library/system.web.mvc.modelvalidatorprovider.aspx
您是否考虑过使用modelValidatorProvider创建模型验证器,而不是使用验证属性?这样您就不依赖于ValidationAttribute,但可以创建自己的验证实现(这将与现有的DataAnnotations验证一起使用)。
http://msdn.microsoft.com/en-us/library/system.web.mvc.modelvalidatorprovider.aspx
除了下面显示的类之外,我也做了类似的事情 IValidatableObject
实现也是如此 (在答案结束时的简短说明而不是完整的代码示例,因为那时答案太长了) - 我已经添加了该类的代码以响应评论 - 它确实得到了答案 非常 很长,但至少你会得到你需要的所有代码。
因为我的目标 ValidationAttribute
在我研究MVC创建的地方的那一刻,我进行了验证 ValidationContext
这得到了 GetValidationResult
该类的方法。
事实证明它在 DataAnnotationsModelValidator
的 Validate
方法:
public override IEnumerable<ModelValidationResult> Validate(object container) {
// Per the WCF RIA Services team, instance can never be null (if you have
// no parent, you pass yourself for the "instance" parameter).
ValidationContext context = new ValidationContext(
container ?? Metadata.Model, null, null);
context.DisplayName = Metadata.GetDisplayName();
ValidationResult result =
Attribute.GetValidationResult(Metadata.Model, context);
if (result != ValidationResult.Success) {
yield return new ModelValidationResult {
Message = result.ErrorMessage
};
}
}
(从MVC3 RTM源复制并重新格式化)
所以我认为这里的一些可扩展性将是有序的:
public class DataAnnotationsModelValidatorEx : DataAnnotationsModelValidator
{
public DataAnnotationsModelValidatorEx(
ModelMetadata metadata,
ControllerContext context,
ValidationAttribute attribute)
: base(metadata, context, attribute)
{
}
public override IEnumerable<ModelValidationResult> Validate(object container)
{
ValidationContext context = CreateValidationContext(container);
ValidationResult result =
Attribute.GetValidationResult(Metadata.Model, context);
if (result != ValidationResult.Success)
{
yield return new ModelValidationResult
{
Message = result.ErrorMessage
};
}
}
// begin Extensibility
protected virtual ValidationContext CreateValidationContext(object container)
{
IServiceProvider serviceProvider = CreateServiceProvider(container);
//TODO: add virtual method perhaps for the third parameter?
ValidationContext context = new ValidationContext(
container ?? Metadata.Model,
serviceProvider,
null);
context.DisplayName = Metadata.GetDisplayName();
return context;
}
protected virtual IServiceProvider CreateServiceProvider(object container)
{
IServiceProvider serviceProvider = null;
IDependant dependantController =
ControllerContext.Controller as IDependant;
if (dependantController != null && dependantController.Resolver != null)
serviceProvider = new ResolverServiceProviderWrapper
(dependantController.Resolver);
else
serviceProvider = ControllerContext.Controller as IServiceProvider;
return serviceProvider;
}
}
所以我先检查一下 IDependant
来自控制器的接口,在这种情况下,我创建一个包装类的实例,充当我之间的适配器 IDependencyResolver
界面和 System.IServiceProvider
。
我以为我也会处理控制器本身的情况 IServiceProvider
也是(在我的情况下不适用 - 但这是一个更通用的解决方案)。
然后我做了 DataAnnotationsModelValidatorProvider
默认使用此验证器,而不是原始验证器:
//register the new factory over the top of the standard one.
DataAnnotationsModelValidatorProvider.RegisterDefaultAdapterFactory(
(metadata, context, attribute) =>
new DataAnnotationsModelValidatorEx(metadata, context, attribute));
现在'正常' ValidationAttribute
基于验证器,可以解决服务:
public class ExampleAttribute : ValidationAttribute
{
protected override ValidationResult
IsValid(object value, ValidationContext validationContext)
{
ICardTypeService service =
(ICardTypeService)validationContext.GetService(typeof(ICardTypeService));
}
}
这仍然是直接的 ModelValidator
- 需要重新实现以支持相同的技术 - 虽然他们已经可以访问 ControllerContext
,所以这不是一个问题。
如果你愿意,还有类似的事情要做 IValidatableObject
- 实现类型,以便在执行期间解析服务 Validate
无需为每种类型继续派生自己的适配器。
ValidatableObjectAdapter
我打电话给它 ValidatableObjectAdapterEx
Validate
和 ConvertResults
该类的私有方法。ValidationContext
是建造的这是代码 ValidatableObjectAdapterEx
- 我希望能更清楚地指出这一点 IDependant
和 ResolverServiceProviderWrapper
这里和之前使用的类型只适用于我的环境 - 如果你使用的是一个静态可访问的全局DI容器,那么重新实现这两个类应该是微不足道的 CreateServiceProvider
方法恰当。
public class ValidatableObjectAdapterEx : ValidatableObjectAdapter
{
public ValidatableObjectAdapterEx(ModelMetadata metadata,
ControllerContext context)
: base(metadata, context) { }
public override IEnumerable<ModelValidationResult> Validate(object container)
{
object model = base.Metadata.Model;
if (model != null)
{
IValidatableObject instance = model as IValidatableObject;
if (instance == null)
{
//the base implementation will throw an exception after
//doing the same check - so let's retain that behaviour
return base.Validate(container);
}
/* replacement for the core functionality */
ValidationContext validationContext = CreateValidationContext(instance);
return this.ConvertResults(instance.Validate(validationContext));
}
else
return base.Validate(container); /*base returns an empty set
of values for null. */
}
/// <summary>
/// Called by the Validate method to create the ValidationContext
/// </summary>
/// <param name="instance"></param>
/// <returns></returns>
protected virtual ValidationContext CreateValidationContext(object instance)
{
IServiceProvider serviceProvider = CreateServiceProvider(instance);
//TODO: add virtual method perhaps for the third parameter?
ValidationContext context = new ValidationContext(
instance ?? Metadata.Model,
serviceProvider,
null);
return context;
}
/// <summary>
/// Called by the CreateValidationContext method to create an IServiceProvider
/// instance to be passed to the ValidationContext.
/// </summary>
/// <param name="container"></param>
/// <returns></returns>
protected virtual IServiceProvider CreateServiceProvider(object container)
{
IServiceProvider serviceProvider = null;
IDependant dependantController = ControllerContext.Controller as IDependant;
if (dependantController != null && dependantController.Resolver != null)
{
serviceProvider =
new ResolverServiceProviderWrapper(dependantController.Resolver);
}
else
serviceProvider = ControllerContext.Controller as IServiceProvider;
return serviceProvider;
}
//ripped from v3 RTM source
private IEnumerable<ModelValidationResult> ConvertResults(
IEnumerable<ValidationResult> results)
{
foreach (ValidationResult result in results)
{
if (result != ValidationResult.Success)
{
if (result.MemberNames == null || !result.MemberNames.Any())
{
yield return new ModelValidationResult { Message = result.ErrorMessage };
}
else
{
foreach (string memberName in result.MemberNames)
{
yield return new ModelValidationResult
{ Message = result.ErrorMessage, MemberName = memberName };
}
}
}
}
}
}
使用该类,您可以将其注册为默认适配器 IValidatableObject
行的实例:
DataAnnotationsModelValidatorProvider.
RegisterDefaultValidatableObjectAdapterFactory(
(metadata, context) => new ValidatableObjectAdapterEx(metadata, context)
);
在MVC 5.2上,你可以 杠杆作用偷 @Andras的回答 和MVC来源和:
DataAnnotationsModelValidatorEx
从 DataAnnotationsModelValidator
namespace System.Web.Mvc
{
// From https://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/DataAnnotationsModelValidator.cs
// commit 5fa60ca38b58, Apr 02, 2015
// Only diff is adding of secton guarded by THERE_IS_A_BETTER_EXTENSION_POINT
public class DataAnnotationsModelValidatorEx : DataAnnotationsModelValidator
{
readonly bool _shouldHotwireValidationContextServiceProviderToDependencyResolver;
public DataAnnotationsModelValidatorEx(
ModelMetadata metadata, ControllerContext context, ValidationAttribute attribute,
bool shouldHotwireValidationContextServiceProviderToDependencyResolver=false)
: base(metadata, context, attribute)
{
_shouldHotwireValidationContextServiceProviderToDependencyResolver =
shouldHotwireValidationContextServiceProviderToDependencyResolver;
}
}
}
public override IEnumerable<ModelValidationResult> Validate(object container)
Validate
创建上下文: -
public override IEnumerable Validate(object container) { // Per the WCF RIA Services team, instance can never be null (if you have // no parent, you pass yourself for the "instance" parameter). string memberName = Metadata.PropertyName ?? Metadata.ModelType.Name; ValidationContext context = new ValidationContext(container ?? Metadata.Model) { DisplayName = Metadata.GetDisplayName(), MemberName = memberName };
#if !THERE_IS_A_BETTER_EXTENSION_POINT
if(_shouldHotwireValidationContextServiceProviderToDependencyResolver
&& Attribute.RequiresValidationContext)
context.InitializeServiceProvider(DependencyResolver.Current.GetService);
#endif
ValidationResult result = Attribute.GetValidationResult(Metadata.Model, context); if (result != ValidationResult.Success) { // ModelValidationResult.MemberName is used by invoking validators (such as ModelValidator) to // construct the ModelKey for ModelStateDictionary. When validating at type level we want to append the // returned MemberNames if specified (e.g. person.Address.FirstName). For property validation, the // ModelKey can be constructed using the ModelMetadata and we should ignore MemberName (we don't want // (person.Name.Name). However the invoking validator does not have a way to distinguish between these two // cases. Consequently we'll only set MemberName if this validation returns a MemberName that is different // from the property being validated. string errorMemberName = result.MemberNames.FirstOrDefault(); if (String.Equals(errorMemberName, memberName, StringComparison.Ordinal)) { errorMemberName = null; } var validationResult = new ModelValidationResult { Message = result.ErrorMessage, MemberName = errorMemberName }; return new ModelValidationResult[] { validationResult }; } return Enumerable.Empty<ModelValidationResult>(); }
DataAnnotationsModelValidatorProvider
城里在您的Global.asax之后 DependencyResolver.SetResolver(new AutofacDependencyResolver(container))
: -
DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
typeof(ValidatorServiceAttribute),
(metadata, context, attribute) => new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));
GetService
在你的 ValidationAttribute
, 例如:public class ValidatorServiceAttribute : ValidationAttribute
{
readonly Type _serviceType;
public ValidatorServiceAttribute(Type serviceType)
{
_serviceType = serviceType;
}
protected override ValidationResult IsValid(
object value,
ValidationContext validationContext)
{
var validator = CreateValidatorService(validationContext);
var instance = validationContext.ObjectInstance;
var resultOrValidationResultEmpty = validator.Validate(instance, value);
if (resultOrValidationResultEmpty == ValidationResult.Success)
return resultOrValidationResultEmpty;
if (resultOrValidationResultEmpty.ErrorMessage == string.Empty)
return new ValidationResult(ErrorMessage);
return resultOrValidationResultEmpty;
}
IModelValidator CreateValidatorService(ValidationContext validationContext)
{
return (IModelValidator)validationContext.GetService(_serviceType);
}
}
允许你在你的模特上拍它: -
class MyModel
{
...
[Required, StringLength(42)]
[ValidatorService(typeof(MyDiDependentValidator),
ErrorMessage = "It's simply unacceptable")]
public string MyProperty { get; set; }
....
}
将它连接到:
public class MyDiDependentValidator : Validator<MyModel>
{
readonly IUnitOfWork _iLoveWrappingStuff;
public MyDiDependentValidator(IUnitOfWork iLoveWrappingStuff)
{
_iLoveWrappingStuff = iLoveWrappingStuff;
}
protected override bool IsValid(MyModel instance, object value)
{
var attempted = (string)value;
return _iLoveWrappingStuff.SaysCanHazCheez(instance, attempted);
}
}
前两者通过以下方式连接:
interface IModelValidator
{
ValidationResult Validate(object instance, object value);
}
public abstract class Validator<T> : IModelValidator
{
protected virtual bool IsValid(T instance, object value)
{
throw new NotImplementedException(
"TODO: implement bool IsValid(T instance, object value)" +
" or ValidationResult Validate(T instance, object value)");
}
protected virtual ValidationResult Validate(T instance, object value)
{
return IsValid(instance, value)
? ValidationResult.Success
: new ValidationResult("");
}
ValidationResult IModelValidator.Validate(object instance, object value)
{
return Validate((T)instance, value);
}
}
我愿意接受更正,但最重要的是,ASP.NET团队,您是否愿意接受PR来添加具有此功能的构造函数 DataAnnotationsModelValidator
?