ASP.NET 구성을 인터페이스에 바인딩

14068 단어 oopcsharpdotnetconfig
ASP.NET 코어를 사용하면 JSON, 메모리 구성 공급자 or your could write you own custom provider. The framework allows you bind configuration from these sources to concrete instances of a class with properties set the configuration values using the IOptions<T> method.

The IOptions<T> allows you register the instantiated configuration class.

A brief recap would be to load upload the config details say for example we are getting these details from the appsettings.json
For .NET 6 the appsettings.json is loaded up automatically.
For this example the appsettings.json would look like this

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "CustomConfigValue": {
    "CustomProperty": "value here"
  }
}

In order to bind the CustomConfigValue node to class/record instance we would need to create a class/record

public class CustomConfigValue
{
    public string CustomProperty { get; set; }
}

then to bind JSON node CustomConfigValue we would do this in the Program.cs

builder.Services.Configure<CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));

Then in the class/controller constructor we set the value from IOptions<T> to the a class property of the same type

private readonly CustomConfigValue _customConfig;

public CustomConfigClass(IOptions<CustomConfigValue> customConfigValue)
{
    _customConfig = customConfigValue.Value;
}
There are a few benefits to using this method, for one it gives us the ease when mocking out the configuration details in our tests, another benefit is configuration reloading when config values change in the JSON file .

Another approach In some case the use of IOptions<T> isn't beneficial and there are a lot of posts about how to bind directly to object instances instead. From our previous example what we would be doing different is that we would be doing this in the Program.cs

CustomConfigValue customConfig = new ();

builder.Configuration.Bind(nameof(CustomConfigValue), customConfig);

builder.Services.AddSingleton(typeof(CustomConfigValue), customConfig);

In the class/controller constructor we would be able to access the CustomConfigValue like this

private readonly CustomConfigValue _customConfig;

public CustomConfigClass(CustomConfigValue customConfigValue)
{
    _customConfig = customConfigValue;
}

This can be abstracted away from the Program.cs by creating a service extension then doing the work like this

public static class ConfigurationExtensions 
{
    public static void AddCustomConfigValue<T>(this IServiceCollection services, IConfigurationSection configSection) where T : class
    {
        T setting = configSection.Get<T>();
        services.TryAddSingleton<T>(setting);
    }
}

In the Program.cs we do this to bind the configuration to the class object

builder.Services.AddCustomConfigValue<CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));

This is all well and good, and covers most use cases, but one thing I find that this method has is yes, you guess it, doesn't bind to an interface. I really love the idea of abstractions, I think it's a concept everyone should apply in every part of the life, but I digress.
In order to bind to an interface let's create another method in the extension class

public static void AddCustomConfigValue<InterfaceT, T>(this IServiceCollection services, IConfigurationSection configSection)
    where T : InterfaceT
    where InterfaceT : class
{
    InterfaceT setting = configSection.Get<T>();
    services.TryAddSingleton(setting);
}

We would add an interface

public interface ICustomConfig
{
    string CustomProperty { get;}
}

Then have the CustomConfigValue implement the interface

public class CustomConfigValue : ICustomConfig

In the Program.cs we would do this to bind the configuration to the object

builder.Services.AddCustomConfigValue<ICustomConfig, CustomConfigValue>(builder.Configuration.GetSection(nameof(CustomConfigValue)));

And in our constructor we would be able to access the configuration with

private readonly ICustomConfig _customConfigViaInterface;

public CustomConfigClass(ICustomConfig customConfigViaInterface)
{
    _customConfigViaInterface= customConfigViaInterface;
}

You could do this to maintain some level of abstraction and aid testing via the binding of the object to an interface and having only get access via the interface.

Another thing to consider is to use the IOptions<T> but bind to a record. The idea behind this is to create an immutable configuration object, once instantiated properties can't be reassigned and enforced at compile time.

등 다양한 구성 공급자의 구성 값을 바인딩할 수 있습니다.

좋은 웹페이지 즐겨찾기