【问题标题】:How can I set `ValidationVisitor.MaxValidationDepth` = 1 only for one Razor Page?如何仅为一个 Razor 页面设置 `ValidationVisitor.MaxValidationDepth` = 1?
【发布时间】:2021-07-25 22:20:37
【问题描述】:

如何为一个 Razor 页面在本地更改 ValidationVisitor.MaxValidationDepth 以防止验证子对象?

我不想用MvcOptions.MaxValidationDepth 全局更改它,就像文档中描述的那样:Maximum recursion

这是一个后续问题。请查看我的原始问题以查看此处的对象结构:​

Razor Page validation error with a complex object, TryValidateModel() does not work

【问题讨论】:

    标签: c# asp.net-core razor-pages


    【解决方案1】:

    我们可以从IObjectModelValidator开始。默认实现是内部的 (DefaultObjectValidator) 并继承自 ObjectModelValidator。基(和抽象)类要求派生类只实现一种称为GetValidationVisitor 的方法。这是您在运行之前修改ValidationVisitorMaxValidationDepth 的一个扩展点。默认实现DefaultObjectValidator 只是将MaxValidationDepth 设置为从MvcOptions 获得的值。所以正如你所说,它在全球范围内应用。

    在您对IObjectModelValidator 的自定义实现中(当然我们让它继承自ObjectModelValidator),您可以改为从当前上下文中获取MaxValidationDepth

    在执行每个页面处理程序之前,您可以设置您选择的MaxValidationDepth。为了使其成为标准(用作横切关注点),我们可以创建一个 IAsyncPageFilter 作为属性,该属性可以应用于您想要的任何页面模型类。

    下面是实现代码:

    //the custom IObjectModelValidator (which is much like what from the source code 
    //of DefaultObjectValidator)
    public class ContextBasedObjectModelValidator : ObjectModelValidator
    {
        readonly MvcOptions _mvcOptions;
        public ContextBasedObjectModelValidator(IModelMetadataProvider modelMetadataProvider, 
                                                IList<IModelValidatorProvider> validatorProviders,
                                                MvcOptions mvcOptions) : base(modelMetadataProvider, validatorProviders)
        {
            _mvcOptions = mvcOptions;
        }
    
        public override ValidationVisitor GetValidationVisitor(ActionContext actionContext, 
            IModelValidatorProvider validatorProvider, 
            ValidatorCache validatorCache, 
            IModelMetadataProvider metadataProvider, 
            ValidationStateDictionary validationState)
        {            
            var visitor = new ValidationVisitor(
                actionContext,
                validatorProvider,
                validatorCache,
                metadataProvider,
                validationState)
            {
                MaxValidationDepth = actionContext.HttpContext.Features.Get<IContextBasedMaxValidationDepthFeature>()?.MaxValidationDepth ?? _mvcOptions.MaxValidationDepth,
                ValidateComplexTypesIfChildValidationFails = _mvcOptions.ValidateComplexTypesIfChildValidationFails,
            };
    
            return visitor;
        }
    }
    
    //we need a class for the custom request feature to hold your context-based MaxValidationDepth
    public interface IContextBasedMaxValidationDepthFeature
    {
        int MaxValidationDepth { get; }
    }
    public class ContextBasedMaxValidationDepthFeature : IContextBasedMaxValidationDepthFeature
    {
        public ContextBasedMaxValidationDepthFeature(int maxValidationDepth)
        {
            MaxValidationDepth = maxValidationDepth;
        }
        public int MaxValidationDepth { get; }
    }
    
    //here the page filter to help set your context-based MaxValidationDepth
    [AttributeUsage(AttributeTargets.Class)]
    public class MaxValidationDepthAttribute : Attribute, IAsyncPageFilter
    {
        public MaxValidationDepthAttribute(int maxValidationDepth)
        {
            MaxValidationDepth = maxValidationDepth;
        }
        public int MaxValidationDepth { get; }
        public Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
        {
            //set the max validation depth from the predefined value (via the attribute)
            context.HttpContext.Features
                   .Set<IContextBasedMaxValidationDepthFeature>(new ContextBasedMaxValidationDepthFeature(MaxValidationDepth));
            return next();
        }
    
        public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
        {
            return Task.CompletedTask;
        }
    }
    

    最后我们需要在Startup.ConfigureServices中注册你的自定义IObjectModelValidator

    services.Replace(new ServiceDescriptor(typeof(IObjectModelValidator), sp => {
                var options = sp.GetRequiredService<IOptions<MvcOptions>>().Value;
                var metadataProvider = sp.GetRequiredService<IModelMetadataProvider>();
                return new ContextBasedObjectModelValidator(metadataProvider, options.ModelValidatorProviders, options);
            }, ServiceLifetime.Singleton));
    

    使用它:

    //suppose you want it to be 1
    //for this specific page
    [MaxValidationDepth(1)]
    public class YourPageModel : PageModel {
         //...
    }
    

    【讨论】:

    • 我认为它可以做得更简单一些。但是,非常感谢您的精彩回答!
    • @Tolbxela 不客气。这并不简单,因为要求有点特殊,当然默认情况下不支持。当您查看源代码时,它变得更加明显(我提到了MaxValidationDepth 的设置方式和位置)。在很多情况下,只有阅读源代码,才能找到答案(通过自定义)。这是asp.net core 的一大优点(易于阅读源代码,易于定制)。一旦你熟悉了它,你会发现它并不复杂,虽然代码相当长:)
    猜你喜欢
    • 2023-02-08
    • 2018-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-09
    • 2020-08-04
    • 2014-03-18
    相关资源
    最近更新 更多