如何在自定义验证属性中获取/注入服务

2024-03-18

我们使用 .NET Core 3.1.5,这是一个 Blazor 服务器应用程序。

我们有一个 ValidationAttribute 并需要访问外部服务来验证对象。

ValidationAttribute 有 IsValid 方法:

protected override ValidationResult IsValid(对象值, ValidationContext validationContext) ValidationContext 有一个 GetService 方法,该方法委托给 ServiceProvider 的实例。 不幸的是,服务提供商字段从未初始化,因此我们无法检索任何服务。

这是在 Mvc 中提出(并修复)的:aspnet/Mvc#6346 但是我们的验证器是通过以下两者之一调用的:

https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs#L47 https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs#L47 https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs#L75 https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs#L75后来在堆栈中,服务提供者也从未被设置。 我犹豫是否要打开一个错误(但可以这样做),但这对我来说似乎是错误的(或者至少应该记录下来)。

任何谷歌搜索最终都会在这篇博客文章中结束,但正如我刚才提到的,这是行不通的。

所以我们的问题是:将服务注入 ValidationAttribute 的正确方法是什么,或者更一般地说,验证需要调用外部服务的模型字段的正确方法是什么?

In statup.cs:

services.AddTransient<IMarktTypDaten, MarktTypDaten>();

我们尝试注入服务并应用验证的类。

public class MarktTypNameValidation : ValidationAttribute {
    protected override ValidationResult IsValid(object value, ValidationContext validationContext) {    
        var service = (IMarktTypDaten) validationContext.GetRequiredService(typeof(IMarktTypDaten));
        ...some code...
        return ValidationResult.Success;
    }
}

调用时出现异常消息GetRequiredService: 'No service for type 'DataAccessLibrary.Interfaces.IMarktTypDaten' has been registered.

它还发布在 Github 上:https://github.com/dotnet/aspnetcore/discussions/23305 https://github.com/dotnet/aspnetcore/discussions/23305

另外:我是 15 年来第一次使用 C#/.NET,请温柔一点;-)


我的团队在自定义验证代码上投入了大量资金,该代码下面使用 DataAnnotations 进行验证。具体来说,我们的自定义验证器(通过大量抽象)取决于 ValidationAttribute.IsValid 方法以及传递给它的 ValidationContext 参数本身就是一个 IServiceProvider 的事实。这对我们在 MVC 中很有用。

我们目前正在将服务器端 Blazor 集成到现有的 MVC 应用程序中,该应用程序已经通过我们的自定义验证实现了许多验证器(全部基于 DataAnnotations),并且我们希望在 Blazor 验证中利用这些验证器。尽管“你不应该这样做”的论点可能是有效的,但如果不进行重大重构,我们就远远超出了该选择。

因此,我进行了更深入的研究,发现我们可以对位于此处的 Microsoft DataAnnotationsValidator.cs 类型进行相对较小的更改。https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/DataAnnotationsValidator.cs https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/DataAnnotationsValidator.cs

真正的变化实际上是位于此处的 EditContextDataAnnotationsExtensions.cs 类型:https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs https://github.com/dotnet/aspnetcore/blob/master/src/Components/Forms/src/EditContextDataAnnotationsExtensions.cs

具体来说,EditContextDataAnnotationsExtensions 方法实际上创建一个新的 ValidationContext 对象,但不会初始化服务提供者。我创建了一个 CustomValidator 组件来替换 DataAnnotationsValidator 组件并复制了大部分流程(我更改了代码以更适合我们的风格,但流程是相同的)。

在我们的 CustomValidator 中,我包含了 ValidationContext 服务提供者的初始化。

        var validationContext = new ValidationContext(editContext.Model);
        validationContext.InitializeServiceProvider(type => this.serviceProvider.GetService(type));

这是我的代码,稍作编辑,但以下内容应该可以开箱即用。

public class CustomValidator : ComponentBase, IDisposable
{
    private static readonly ConcurrentDictionary<(Type ModelType, string FieldName), PropertyInfo> PropertyInfoCache = new ConcurrentDictionary<(Type, string), PropertyInfo>();

    [CascadingParameter] EditContext CurrentEditContext { get; set; }
    [Inject] private IServiceProvider serviceProvider { get; set; }
    
    private ValidationMessageStore messages;

    protected override void OnInitialized()
    {
        if (CurrentEditContext == null)
        {
            throw new InvalidOperationException($"{nameof(CustomValidator)} requires a cascading " +
                                                $"parameter of type {nameof(EditContext)}. For example, you can use {nameof(CustomValidator)} " + "inside an EditForm.");
        }

        this.messages = new ValidationMessageStore(CurrentEditContext);

        // Perform object-level validation on request
        CurrentEditContext.OnValidationRequested += validateModel;

        // Perform per-field validation on each field edit
        CurrentEditContext.OnFieldChanged += validateField;
    }

    private void validateModel(object sender, ValidationRequestedEventArgs e)
    {
        var editContext = (EditContext) sender;
        var validationContext = new ValidationContext(editContext.Model);
        validationContext.InitializeServiceProvider(type => this.serviceProvider.GetService(type));
        var validationResults = new List<ValidationResult>();
        Validator.TryValidateObject(editContext.Model, validationContext, validationResults, true);

        // Transfer results to the ValidationMessageStore
        messages.Clear();
        foreach (var validationResult in validationResults)
        {
            if (!validationResult.MemberNames.Any())
            {
                messages.Add(new FieldIdentifier(editContext.Model, fieldName: string.Empty), validationResult.ErrorMessage);
                continue;
            }

            foreach (var memberName in validationResult.MemberNames)
            {
                messages.Add(editContext.Field(memberName), validationResult.ErrorMessage);
            }
        }

        editContext.NotifyValidationStateChanged();
    }

    private void validateField(object? sender, FieldChangedEventArgs e)
    {
        if (!TryGetValidatableProperty(e.FieldIdentifier, out var propertyInfo)) return;

        var propertyValue = propertyInfo.GetValue(e.FieldIdentifier.Model);
        var validationContext = new ValidationContext(CurrentEditContext.Model) {MemberName = propertyInfo.Name};
        validationContext.InitializeServiceProvider(type => this.serviceProvider.GetService(type));

        var results = new List<ValidationResult>();
        Validator.TryValidateProperty(propertyValue, validationContext, results);
        messages.Clear(e.FieldIdentifier);
        messages.Add(e.FieldIdentifier, results.Select(result => result.ErrorMessage));

        // We have to notify even if there were no messages before and are still no messages now,
        // because the "state" that changed might be the completion of some async validation task
        CurrentEditContext.NotifyValidationStateChanged();
    }

    private static bool TryGetValidatableProperty(in FieldIdentifier fieldIdentifier, [NotNullWhen(true)] out PropertyInfo propertyInfo)
    {
        var cacheKey = (ModelType: fieldIdentifier.Model.GetType(), fieldIdentifier.FieldName);

        if (PropertyInfoCache.TryGetValue(cacheKey, out propertyInfo)) return true;

        // DataAnnotations only validates public properties, so that's all we'll look for
        // If we can't find it, cache 'null' so we don't have to try again next time
        propertyInfo = cacheKey.ModelType.GetProperty(cacheKey.FieldName);

        // No need to lock, because it doesn't matter if we write the same value twice
        PropertyInfoCache[cacheKey] = propertyInfo;

        return propertyInfo != null;
    }

    public void Dispose()
    {
        if (CurrentEditContext == null) return;
        CurrentEditContext.OnValidationRequested -= validateModel;
        CurrentEditContext.OnFieldChanged -= validateField;
    }
}

添加此类型后所需要做的就是在 blazor/razor 文件中使用它而不是 DataAnnotationsValidator。

所以代替这个:

<DataAnnotationsValidator />

do this:

<CustomValidator />
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在自定义验证属性中获取/注入服务 的相关文章

随机推荐

  • 如何让 iPhone 应用程序用户提交崩溃报告? [复制]

    这个问题在这里已经有答案了 可能的重复 iPhone如何获取客户的崩溃日志 https stackoverflow com questions 3844482 iphone how to get crash log from custome
  • 在 JavaScript 中,当完成通过 new ActiveXObject 创建的对象后,我是否需要将其设置为 null?

    在 WSH 中运行并创建对象 例如 Scripting FileSystemObject 或任何任意 COM 对象 的 Javascript 程序中 完成后是否需要将变量设置为 null 例如 我建议这样做 var fso new Acti
  • 如何将开始按钮放在剪辑的中间

    我在以下位置找到了设计师页面http www videojs com http www videojs com 在那里 您可以将开始按钮的位置更改为剪辑的中间 但我无法在 video js 播放器上放置或使用此样式表 有人可以解释一下如何在
  • C++ 中获取用户输入未执行/跳过的代码

    在下面的代码中 当我尝试让用户输入他们的名字时遇到错误 我的程序只是跳过它并直接进行函数调用 而不允许用户输入他们的名字 尽管出现错误 我的程序仍在编译 我不确定出了什么问题 因为我是根据在这里找到的其他示例编写该部分的 有什么建议么 in
  • 使用 Moneta (JavaMoney) JSR 354 实现自定义货币金额格式

    我真的很困惑如何定制MonetaryAmountFormat使用 Moneta JSR 354 实现 我的目的是能够解析两者1 23 and 3 45 as MonetaryAmounts 这是我的单元测试 Test public void
  • 从 WidgetKit 小部件扩展检测应用程序启动

    点击 WidgetKit 小部件会自动启动其父应用程序 如何检测我的应用程序是否是从其 WidgetKit 小部件扩展启动的 我无法找到任何有关在应用程序中捕获此内容的文档AppDelegate and or SceneDelegate 要
  • 如何使用参数和 POST 方法重定向到外部 url?

    我想在提交到 Flask 中的操作 url 之前保存表单数据
  • 如何在android中打开图库中的一个特定文件夹?

    我使用下面的代码打开 Android 默认图库应用程序 它会打开 sdcard 下的所有图像文件夹 如何只打开一个特定文件夹 Intent intent new Intent Intent ACTION PICK android provi
  • 除非作为参数传入,否则无法添加快速路线

    我正在尝试创建映射其自己的路线的节点模块 我在下面提供了一个简化的示例 删除了返回的对象中映射的所有其他函数以及任何代码以简化示例 我最初的尝试是这样做 文件 web core js function initRoutes app var
  • 具有不同 uv 坐标的 OpenGL ES 1 多重纹理

    我需要使用多重纹理渲染一个对象 但两个纹理对于同一对象具有不同的 uv 坐标 一张是法线贴图 另一张是光照贴图 请提供与此相关的任何有用材料 在 OpenGL ES 2 中 无论如何你都会使用着色器 因此 您可以完全自由地使用您喜欢的任何纹
  • 为 RDBMS(MySQL 数据库)创建 SPARQL 端点的最佳方法

    我正在 想做 一些链接开放数据集的实验 特别是政府推出的实验 我有一个 RDBMS 更具体地说是 MySQL 我设计它时考虑了语义网络的想法 即我将信息存储为对象 谓词和定义对象的类 反过来 所有对象通过主语 gt 谓词 gt 宾语形式的语
  • OpenId + 记住我/保持登录状态

    我有一个问题 关于如何 什么是使用 OpenId 并提供保持登录状态的最佳方法 例如 如果我查看 Stackoverflow 我已经使用 Google 登录 如果我关闭浏览器并返回 它仍然显示我已登录 However 我没有登录谷歌 而且我
  • Rails 引擎存在外键问题

    我正在开发一个 Rails 引擎 这是我的gem gemspec s required ruby version gt 2 0 0 s add dependency rails gt 4 2 0 s add dependency enume
  • angularjs - 在范围或 ng-model 上使用“字符串”名称

    在普通的 JavaScript 中 你可以像这样声明变量 var obj obj item text obj item text 这里给出的例子 http jsbin com petafu 1 edit http jsbin com pet
  • Google Play 排行榜 UI 颜色更改

    在我刚刚开发的游戏中 直到一周前 排行榜 UI 颜色还是深半透明的绿色 这看起来感觉很好 至少对眼睛不刺激 突然之间 它变成了明亮的半透明红色 并且从那时起就一直保持这种颜色 这种颜色与我游戏的任何阶段的任何屏幕都不匹配 这是我的排行榜屏幕
  • 如何在 Dart 中创建 HTML 链接?

    我想用 Dart 创建一个 HTML 链接 在 HTML 中我会写 You can click a href url 1 here a and a href url 2 there a 我不知道如何在 Dart 中做到这一点 我尝试过类似的
  • Python xlwt 创建错误的 Excel 书

    我正在尝试使用xlwt创建具有多个选项卡的输出文件 xlsx 格式 我的Python版本号是2 7 我使用Aptana Studio 3作为IDE 我用过xlwt包之前 具有相同的环境 执行相同的任务 效果很好 但这一次 一开始运行良好 然
  • 将 gcc libs .data 放在特定部分?

    我正在尝试为我们的嵌入式系统切换到 GNU GCC 编译器 但由于我们芯片的内存布局被分割 我在链接该项目时遇到了问题 RAM section 1 0x10000 0x12FFF RAM section 2 0x18000 0x1BFFF
  • Express js中通过id删除mongodb文档

    我正在为此抓狂 尝试通过 id 删除文档 router delete api menu delete id function req res var id req params id db get collection menu funct
  • 如何在自定义验证属性中获取/注入服务

    我们使用 NET Core 3 1 5 这是一个 Blazor 服务器应用程序 我们有一个 ValidationAttribute 并需要访问外部服务来验证对象 ValidationAttribute 有 IsValid 方法 protec