如何使用带有 ValidateDataAnnotations 的配置

Eti*_*and 6 c# asp.net-mvc asp.net-core

我已经阅读了OptionsConfiguration基础知识的 Microsoft 文档,但仍然找不到在验证数据注释时将配置提取到对象中的正确方法。

我尝试过的一种方法 Startup.ConfigureServices

services.AddOptions<EmailConfig>().Bind(Configuration.GetSection("Email")).ValidateDataAnnotations();
Run Code Online (Sandbox Code Playgroud)

这“应该”允许通过在类构造函数中添加它来访问配置: (IOptions<EmailConfig> emailConfig)

但是它不起作用。

另一种方法是添加(IConfiguration configuration)到构造函数中,但这不允许我调用ValidateDataAnnotations.

configuration.GetSection("Email").Get<EmailConfig>();
Run Code Online (Sandbox Code Playgroud)

第一个问题:绑定和验证配置的责任属于 Startup 类还是使用它的类?如果它被多个类使用,我会说它属于 Startup;并且该类可以在具有不同配置布局的另一个项目中使用。

第二个问题:绑定和验证配置以便可以从类访问它的正确语法是什么?

第三个问题:如果我在 Startup 中通过数据注释进行验证,那么使用该配置的类会简单地假设配置是有效的,并且我不进行任何重新验证?

更新:在获得更多经验并查看我所有代码的结构后,我改变了我的方法以遵循标准模式。

以下代码确实有效...但仅在使用时对其进行验证。这可以在类库中注册,并且在使用特定服务之前不会抛出任何错误。

services.AddOptions<EmailConfig>()
    .Bind(configuration.GetSection("Email"))
    .ValidateDataAnnotations();
Run Code Online (Sandbox Code Playgroud)

然后,在配置中,我添加它以在启动时强制验证所需的配置值(CheckNotNull 是一个自定义扩展方法,重要的是您调用 IOptions.Value

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app?.ApplicationServices.GetService<IOptions<EmailConfig>>().Value.CheckNotNull("Config: Email");
    app?.ApplicationServices.GetService<IOptions<OntraportConfig>>().Value.CheckNotNull("Config: Ontraport");
    ...
Run Code Online (Sandbox Code Playgroud)

然后在课堂上使用它

public class EmailService(IOptions<EmailConfig> config)
Run Code Online (Sandbox Code Playgroud)

hik*_*uen 9

来自未来的更新

较新版本的 .NET 添加了更多扩展方法来简化此过程。

注意:从技术上讲,这些都来自Microsoft.Extensions.XYZ与 .NET 一起发布的包。这些包也可能与早期版本的 .NET 兼容,但我尚未验证向后兼容性。

OP的例子

services.AddOptions<EmailConfig>()
    .Bind(configuration.GetSection("Email"))
    .ValidateDataAnnotations();
Run Code Online (Sandbox Code Playgroud)

现在可以简化为:

// Requires .NET 5 extensions or greater
services.AddOptions<EmailConfig>()
    .BindConfiguration("Email")
    .ValidateDataAnnotations();
Run Code Online (Sandbox Code Playgroud)

...并且为了在启动时(而不是使用选项时)进行急切验证,我们可以添加一行:

// Requires .NET 6 extensions or greater
services.AddOptions<EmailConfig>()
    .BindConfiguration("Email")
    .ValidateDataAnnotations()
    .ValidateOnStart();
Run Code Online (Sandbox Code Playgroud)

来源/来源

我从 Andrew Lock 的博客文章中了解了这些更新。信任和感谢归于他:Addingvalidationtostrongtypedconfigurationobjectsin.NET6


Nko*_*osi 6

在将它添加到服务集合之前,您可以尝试在启动时自己验证该类。

启动

var settings = Configuration.GetSection("Email").Get<EmailConfig>();

//validate
var validationResults = new List<ValidationResult>();
var validationContext = new ValidationContext(settings, serviceProvider: null, items: null);
if (!Validator.TryValidateObject(settings, validationContext, validationResults, 
        validateAllProperties: true)) {
    //...Fail early
    //will have the validation results in the list
}

services.AddSingleton(settings);
Run Code Online (Sandbox Code Playgroud)

这样你就不会被耦合,IOptions并且你也允许你的代码提前失败,你可以在需要的地方显式注入依赖项。

您可以将验证打包到您自己的扩展方法中,例如

public static T GetValid<T>(this IConfiguration configuration) {
    var obj = configuration.Get<T>();    
    //validate
     Validator.ValidateObject(obj, new ValidationContext(obj), true);    
    return obj;
}
Run Code Online (Sandbox Code Playgroud)

对于像这样的电话

EmailConfig emailSection = Configuration.GetSection("Email").GetValid<EmailConfig>();
services.AddSingleton(emailSection);
Run Code Online (Sandbox Code Playgroud)

在内部,ValidateDataAnnotations基本上是在做同样的事情。

/// <summary>
/// Validates a specific named options instance (or all when name is null).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>The <see cref="ValidateOptionsResult"/> result.</returns>
public ValidateOptionsResult Validate(string name, TOptions options)
{
    // Null name is used to configure all named options.
    if (Name == null || name == Name)
    {
        var validationResults = new List<ValidationResult>();
        if (Validator.TryValidateObject(options,
            new ValidationContext(options, serviceProvider: null, items: null), 
            validationResults, 
            validateAllProperties: true))
        {
            return ValidateOptionsResult.Success;
        }

        return ValidateOptionsResult.Fail(String.Join(Environment.NewLine,
            validationResults.Select(r => "DataAnnotation validation failed for members " +
                String.Join(", ", r.MemberNames) +
                " with the error '" + r.ErrorMessage + "'.")));
    }

    // Ignored if not validating this instance.
    return ValidateOptionsResult.Skip;
}
Run Code Online (Sandbox Code Playgroud)

源代码