Asp.Net MVC3:在ValidationContext中设置自定义IServiceProvider,以便验证器可以解析服务

And*_*tan 11 .net c# asp.net-mvc inversion-of-control

2012年12月18日更新

由于这个问题似乎得到了不少观点,我应该指出,接受的答案不是我使用的解决方案,但它确实提供了构建解决方案的链接和资源,但在我看来,并非理想的解决方案.我的回答包含 MVC框架标准部分的替换 ; 并且你应该只使用那些,如果你觉得它们仍然适用于未来的版本(一些私有代码被从官方资源中删除,因为基类中没有足够的可扩展性).

但是,我可以确认这两个类也适用于Asp.Net MVC 4以及3.

我也可以重复Asp.Net Web API框架的类似实现,这是我最近所做的.

结束更新

我有一个类型,有很多'标准'验证(需要等),但也有一些自定义验证.

一些此验证需要抓住服务对象并使用其他属性之一作为关键字查找某些较低级别(即"模型层"下方)元数据.然后,元数据控制是否需要一个或多个属性以及这些属性的有效格式.

更具体一点 - 类型是卡片支付对象,简化为两个有问题的属性,如下所示:

public class CardDetails
{
  public string CardTypeID { get; set; }
  public string CardNumber { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后我有一个服务:

public interface ICardTypeService
{
  ICardType GetCardType(string cardTypeID);
}
Run Code Online (Sandbox Code Playgroud)

ICardType 然后包含不同的信息 - 这里的两个至关重要:

public interface ICardType
{
  //different cards support one or more card lengths
  IEnumerable<int> CardNumberLengths { get; set; }
  //e.g. - implementation of the Luhn algorithm
  Func<string, bool> CardNumberVerifier { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我的控制器都能够ICardTypeService使用标准模式来解决问题

 var service = Resolve<ICardTypeService>();
Run Code Online (Sandbox Code Playgroud)

(虽然我应该提一下这个电话背后的框架是专有的)

他们通过使用通用接口获得

public interface IDependant
{
  IDependencyResolver Resolver { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后,我的框架负责在构造控制器实例时(由另一个解析器或MVC标准控制器工厂)分​​配可用于控制器实例的最具体的依赖项解析器.Resolve最后一个代码块中的那个方法是这个Resolver成员的简单包装器.

所以 - 如果我可以抓住ICardType从浏览器收到的付款,我可以对卡号长度等进行初步检查.问题是,如何从覆盖的IsValid(object, ValidationContext)覆盖范围内解决服务ValidationAttribute

我需要将当前控制器的依赖解析器传递给验证上下文.我看到这ValidationContext两个实现IServiceProvider并且有一个实例IServiceContainer- 很明显我应该能够为我的服务解析器创建一个包装器,它也可以实现其中一个(可能IServiceProvider).

我已经注意到,在ValidationContextMVC框架生成a的所有地方,服务提供者总是传递null.

那么,在MVC管道中的哪一点,我应该寻求覆盖核心行为并注入我的服务提供者?

我应该补充一点,这不是我需要做这样的事情的唯一场景 - 所以理想情况下我想要一些我可以应用于管道的东西,以便所有ValidationContexts都配置当前服务提供商用于当前控制器.

Rub*_*ink 5

在MVC 5.2上,您可以利用窃取@Andras的答案和MVC源代码并:

1. 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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

2.克隆基础impl public override IEnumerable<ModelValidationResult> Validate(object container)

3. 应用hackValidate创建上下文后渲染优雅的切口: -

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
Run Code Online (Sandbox Code Playgroud)
   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>();
}
Run Code Online (Sandbox Code Playgroud)

告诉MVC关于DataAnnotationsModelValidatorProvider城里的新事物

在您的Global.asax之后DependencyResolver.SetResolver(new AutofacDependencyResolver(container)): -

DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
    typeof(ValidatorServiceAttribute),
    (metadata, context, attribute) => new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));
Run Code Online (Sandbox Code Playgroud)

5.利用你的想象力滥用你的新服务定位器消费使用GetService你的ctor注入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);
    }
}
Run Code Online (Sandbox Code Playgroud)

允许你在模型上拍它: -

class MyModel 
{
    ...
    [Required, StringLength(42)]
    [ValidatorService(typeof(MyDiDependentValidator), 
        ErrorMessage = "It's simply unacceptable")]
    public string MyProperty { get; set; }
    ....
}
Run Code Online (Sandbox Code Playgroud)

将它连接到:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

前两者通过以下方式连接:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我愿意接受更正,但最重要的是,ASP.NET团队,您是否愿意接受PR来添加具有此功能的构造函数DataAnnotationsModelValidator


Lin*_*ron 3

您是否考虑过使用 modelValidatorProvider 而不是使用验证属性来创建模型验证器?这样,您就不再依赖 ValidationAttribute,而是可以创建自己的验证实现(这将与现有的 DataAnnotations 验证一起工作)。

http://msdn.microsoft.com/en-us/library/system.web.mvc.modelvalidatorprovider.aspx

http://dotnetslackers.com/articles/aspnet/Experience-ASP-NET-MVC-3-Beta-the-New-Dependency-Injection-Support-Part2.aspx#s10-new-support-for-validator-provider

http://dotnetslackers.com/articles/aspnet/Customizing-ASP-NET-MVC-2-Metadata-and-Validation.aspx#s2-validation