C# 8.0 不可为空的引用类型和选项模式

Mon*_*ana 7 c# asp.net asp.net-core nullable-reference-types

Tl; dr:我想要一个选项类,它的成员使用不可为空的类型,没有默认值。

C# 8.0 引入了可空引用类型。

我发现在 ASP.Net选项模式中使用可为空的引用类型相当困难、不完整,或者我遗漏了一些东西。我遇到了这个stack over flow post 中描述的相同问题。

  1. 我们不想让 Name 可以为空,因为我们需要在任何地方进行传统的空检查(这违背了不可空引用类型的目的)
  2. 我们无法创建构造函数来强制使用不可为空的名称值创建 MyOptions 类,因为 Configure 方法为我们构造了选项实例
  3. 我们不能使用 null-forgiving 操作符技巧 (public string name { get; set; } = null!;) 因为这样我们就不能确保 Name 属性被设置,我们最终会在 Name 属性中得到一个空值不会出现这种情况的地方(在服务内部)

我想要一个选项类,它的成员使用不可为空的类型,没有默认值。无论如何,该帖子中的答案最终都使用可空类型(我试图避免)或默认值(我也试图避免)。

关于选项验证的评论提出了好点,看起来很有希望,但事实证明该Validate方法仍然需要一个选项对象来验证,如果您已经必须将选项对象传递给它,这将违背目的。

public ValidateOptionsResult Validate(string name, MyOptions options)
 // Pointless if MyOptions options is being passed in here
Run Code Online (Sandbox Code Playgroud)

这是毫无意义的,因为我已经确定强制使用所有不可为空成员且没有默认值的选项类的唯一方法是使用构造函数。以下面的代码示例为例。

public ValidateOptionsResult Validate(string name, MyOptions options)
 // Pointless if MyOptions options is being passed in here
Run Code Online (Sandbox Code Playgroud)

因此,如果我需要一个带有类参数的构造函数,那么我可以使用以下内容自行实例化它:

namespace SenderServer.Options
{
    using System;
    using Microsoft.Extensions.Configuration;

    /// <summary>
    /// Configuration options for json web tokens.
    /// </summary>
    public class JwtOptions
    {
        /// <summary>
        /// The secret used for signing the tokens.
        /// </summary>
        public String Secret { get; }

        /// <summary>
        /// The length of time in minutes tokens should last for.
        /// </summary>
        public Int32 TokenExpirationInMinutes { get; }

        /// <summary>
        /// Configuration options for json web tokens.
        /// </summary>
        /// <param name="secret"> The secret used for signing the tokens.</param>
        /// <param name="tokenExpirationInMinutes">The length of time in minutes tokens should last for.</param>
        public JwtOptions(String secret, Int32 tokenExpirationInMinutes)
        {
            Secret = secret;
            TokenExpirationInMinutes = tokenExpirationInMinutes;
        }

        /// <summary>
        /// Create a JwtOptions instance from a configuration section.
        /// </summary>
        /// <param name="jwtConfiguration">The configuration section.</param>
        /// <returns>A validated JwtOptions instance.</returns>
        public static JwtOptions FromConfiguration(IConfiguration jwtConfiguration)
        {
            // Validate the secret
            String? secret = jwtConfiguration[nameof(Secret)];
            if (secret == null)
            {
                throw new ArgumentNullException(nameof(Secret));
            }

            // Validate the expiration length
            if (!Int32.TryParse(jwtConfiguration[nameof(TokenExpirationInMinutes)], out Int32 tokenExpirationInMinutes))
            {
                throw new ArgumentNullException(nameof(TokenExpirationInMinutes));
            }

            if (tokenExpirationInMinutes < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(TokenExpirationInMinutes));
            }

            return new JwtOptions(secret, tokenExpirationInMinutes);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但那我该放在哪里jwtOptions呢?services.Configure<JwtOptions>(jwtOptions);和变体都没有接受已经实例化的对象(或者至少没有我见过的)。最后,即使他们这样做了,您也不能使用没有公共无参数构造函数的依赖注入选项类。

// Configure the JWT options
IConfiguration jwtConfiguration = Configuration.GetSection("Authentication:JwtOptions");
JwtOptions jwtOptions = JwtOptions.FromConfiguration(jwtConfiguration); // This performs validation as well
Run Code Online (Sandbox Code Playgroud)

pok*_*oke 4

\n

我想要一个选项类,它的成员使用不可为空的类型,没有默认值。

\n
\n

不幸的是,Microsoft.Extensions.Options 根本不适合您。选项的工作方式是拥有一个由多个源、操作和验证器组成的配置管道,这些源、操作和验证器都使用相同的选项对象。由于该管道没有明确的开始,并且任何配置源都可以位于管道中的任何位置,因此选项对象的构造由框架处理,并且在调用任何配置源之前进行。

\n

为了让选项允许不同类型的用例,这是绝对必要的:您可以从配置(Microsoft.Extensions.Configuration)配置选项,您可以通过配置操作配置它们,您可以通过具有附加功能的服务配置它们依赖项等。所有这些都可以按任何顺序运行。

\n

因此,由于对象的构造是由框架进行的,因此还需要创建选项对象的默认值:通常,这些只是 type\xe2\x80\x99sdefault值,但您也可以通过对象选择不同的默认值\xe2\x80\x99s 构造函数。

\n

如果要强制在管道之后配置特定参数,可以使用配置后操作来强制配置,或使用选项验证来验证配置的选项。但由于这一切都在管道中运行,因此您需要有默认值。

\n

所以基本上,如果您需要具有没有默认值的不可空属性,那么您不能使用Options。至少不是开箱即用的。如果您想这样做是为了安全地引用服务中的选项,那么可以采用不同的方法来解决此问题:直接注入IOptions<T>不可为空的选项对象,而不是注入T。并通过工厂提供:

\n
services.AddSingleton<MySafeOptions>(sp =>\n{\n    var options = sp.GetService<IOptions<MyUnsafeOptions>>();\n    return new MySafeOptions(options.Value);\n});\nservices.Configure<MyUnsafeOptions>(Configuration.GetSection("MyOptions"));\n
Run Code Online (Sandbox Code Playgroud)\n