在ASP.NET 5中获取配置值(vNext)

时间:2022-12-02 11:47:36

I am struggling with some concepts in ASP.NET 5 (vNext).

我正在努力解决ASP.NET 5(vNext)中的一些概念。

One of those is the Dependency Injection approach used for configuration. It seems like I have to pass a parameter all the way through the stack. I'm probably misunderstanding something or doing it wrong.

其中之一是用于配置的依赖注入方法。好像我必须在堆栈中一直传递一个参数。我可能误解了某些事情或做错了。

Imagine I have a config property named "contactEmailAddress". I'll use that config property to send an email when a new order is placed. With that scenario in mind, my ASP.NET 5 stack will look like this:

想象一下,我有一个名为“contactEmailAddress”的配置属性。我将使用该配置属性在发出新订单时发送电子邮件。考虑到这种情况,我的ASP.NET 5堆栈将如下所示:

Startup.cs

Startup.cs

public class Startup
{        
  public IConfiguration Configuration { get; set; }
  public Startup(IHostingEnvironment environment)
  {
    var configuration = new Configuration().AddJsonFile("config.json");
    Configuration = configuration;
  }

  public void ConfigureServices(IServiceCollection services)
  {
    services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
    services.AddMvc();
  }

  public void Configure(IApplicationBuilder app)
  {
    app.UseErrorPage();
    app.UseMvc(routes =>
      {
        routes.MapRoute("default",
          "{controller}/{action}/{id}",
          defaults: new { controller = "Home", action = "Index" });
      }
    );
    app.UseWelcomePage();
  }

AppSettings.cs

AppSettings.cs

public class AppSettings
{
 public string ContactEmailAddress { get; set; }
}

config.json

config.json

{
  "AppSettings": {
    "ContactEmailAddress":"support@mycompany.com"  
  }
}

OrderController.cs

OrderController.cs

[Route("orders")]
public class OrdersController : Controller
{    
  private IOptions<AppSettings> AppSettings { get; set; }

  public OrdersController(IOptions<AppSettings> appSettings)
  {
    AppSettings = appSettings;
  }

  [HttpGet("new-order")]
  public IActionResult OrderCreate()
  {
    var viewModel = new OrderViewModel();
    return View(viewModel);
  }

  [HttpPost("new-order")]
  public IActionResult OrderCreate(OrderViewModel viewModel)
  {

    return new HttpStatusCodeResult(200);
  } 
}

Order.cs

Order.cs

public class Order()
{
  public void Save(IOptions<AppSettings> appSettings)
  {
    // Send email to address in appSettings
  }

  public static List<Order> FindAll(IOptions<AppSettings> appSettings)
  {
    // Send report email to address in appSettings
    return new List<Order>();
  }
}

As the example above shows, I'm passing AppSettings through the entire stack. This does not feel correct. To further my worries, this approach will not work if I'm attempt to use a third-party library that needs to access configuration settings. How can a third-party library access configuration settings? Am I misunderstanding something? Is there a better way to do this?

如上例所示,我正在通过整个堆栈传递AppSettings。这感觉不正确。为了进一步担心,如果我尝试使用需要访问配置设置的第三方库,这种方法将无效。第三方库如何访问配置设置?我误会了什么吗?有一个更好的方法吗?

2 个解决方案

#1


11  

You are entangling 2 different run time resource provider, AppSettings and Dependency Injection.

您正在纠缠2个不同的运行时资源提供程序,AppSettings和依赖注入。

AppSettings, provides run-time access to Application specific values like UICulture strings, Contact Email, etc.

AppSettings,提供对特定于应用程序的值的运行时访问,如UICulture字符串,联系电子邮件等。

DI Containers are factories that Manage access to Services and their lifetime scopes. For example, If a MVC Controller needed access to your EmailService, you would configure

DI容器是管理服务访问及其生命周期范围的工厂。例如,如果MVC Controller需要访问您的EmailService,您将进行配置

   public void ConfigureServices(IServiceCollection services)
   {
      // Add all dependencies needed by Mvc.
      services.AddMvc();

      // Add EmailService to the collection. When an instance is needed,
      // the framework injects this instance to the objects that needs it
      services.AddSingleton<IEmailService, EmailService>();
   }

Then, if our Home Controller needs access to your EmailService, we add a dependency on it's Interface by adding it as a parameter to the Controller constructor

然后,如果我们的Home Controller需要访问您的EmailService,我们通过将它作为参数添加到Controller构造函数来添加对它的接口的依赖性

public class HomeController : Controller
{
   private readonly IEmailService _emailService;
   private readonly string _emailContact;

  /// The framework will inject an instance of an IEmailService implementation.
   public HomeController(IEmailService emailService)
   {
      _emailService = emailService;
      _emailContact = System.Configuration.ConfigurationManager.
                   AppSettings.Get("ContactEmail");
   }

   [HttpPost]
   public void EmailSupport([FromBody] string message)
   {
      if (!ModelState.IsValid)
      {
         Context.Response.StatusCode = 400;
      }
      else
      {
         _emailService.Send(_emailContact, message);

The purpose of Dependancy Injection is to manage access and lifetimes of services.

Dependancy Injection的目的是管理服务的访问和生命周期。

In the previous example, in our Application Startup, we configured the DI Factory to associate application requests for IEmailService with EmailService. So when our Controllers are instantiate by the MVC Framework, the framework notices that our Home Controller expects IEmailService, the framework checks our Application Services Collection. It finds mapping instructions and Inject a Singleton EmailService (a descendant of the occupying Interface) into our Home Controller.

在前面的示例中,在我们的应用程序启动中,我们将DI Factory配置为将IEmailService的应用程序请求与EmailService相关联。因此,当我们的控制器由MVC框架实例化时,框架会注意到我们的Home Controller需要IEmailService,框架会检查我们的Application Services Collection。它找到映射指令并将Singleton EmailService(占用接口的后代)注入我们的Home Controller。

Super Polymorphic Factorific - alodocious!

超级多态因子 - 太过分了!

Why is this important?

为什么这很重要?

If your contact email changes, you change the AppSetting value and are done. All requests for "ContactEmail" from ConfigurationManager are Globally changed. Strings are easy. No need for Injection when we can just hash.

如果您的联系电子邮件发生更改,则更改AppSetting值并完成。 ConfigurationManager中对“ContactEmail”的所有请求都是全局更改的。字符串很简单。我们可以哈希时不需要注入。

If your Repository, Email Service, Logging Service, etc changes, you want a Global way to change all references to this service. Service reference aren't as easily transferred as immutable string literals. Service instantiation should be handled by a factory to configure the Service's settings and dependencies.

如果您的存储库,电子邮件服务,日志记录服务等发生更改,您需要一种全局方式来更改对此服务的所有引用。服务引用不像不可变字符串文字那样容易转移。服务实例化应由工厂处理,以配置服务的设置和依赖关系。

So, in a year you develop a RobustMailService:

所以,在一年内你开发了一个RobustMailService:

Class RobustMailService : IEmailService
{

....

}

As long as your new RobustMailService inherits and implements the IEmailService Interface, you can substitute all references to your mail service Globally by changing :

只要您的新RobustMailService继承并实现了IEmailService接口,您就可以通过更改以下内容替换所有对您的邮件服务的引用:

   public void ConfigureServices(IServiceCollection services)
   {
      // Add all dependencies needed by Mvc.
      services.AddMvc();

      // Add RobustMailService to the collection. When an instance is needed,
      // the framework injects this instance to the objects that needs it 
      services.AddSingleton<IEmailService, RobustMailService>();
   }

#2


3  

This can be achieved using IOptions assessor service as it seems you were trying.

这可以使用IOptions评估服务来实现,因为您似乎正在尝试。

We can begin by creating a class with all of the variables that your controller needs from configuration.

我们可以从创建一个包含控制器所需的所有变量的类开始。

public class VariablesNeeded
{
    public string Foo1{ get; set; }        
    public int Foo2{ get; set; }
}

public class OtherVariablesNeeded
{
    public string Foo1{ get; set; }        
    public int Foo2{ get; set; }
}

We now need to tell the middleware that the controller needs this class in the constructor of the controller using dependency injection, we do this using IOptions accessor service.

我们现在需要告诉中间件控制器需要使用依赖注入在控制器的构造函数中使用此类,我们使用IOptions访问器服务。

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

public class MyController: Controller{    
    private readonly VariablesNeeded _variablesNeeded;

    public MyController(IOptions<VariablesNeeded> variablesNeeded) {
        _variablesNeeded= variablesNeeded.Value;
    }

    public ActionResult TestVariables() {
        return Content(_variablesNeeded.Foo1 + _variablesNeeded.Foo2);
    }
}

To get the variables from your configuration files, we create a constructor for the startup class, and a configuration property.

要从配置文件中获取变量,我们为启动类和配置属性创建构造函数。

public IConfigurationRoot Configuration { get; }

public Startup(IHostingEnvironment env)
{
    /* This is the fairly standard procedure now for configuration builders which will pull from appsettings (potentially with an environmental suffix), and environment variables. */
    var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)    
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
    Configuration = builder.Build();
}

Now we need to make sure the pipeline actually supplies the controller with this service.

现在我们需要确保管道实际上为控制器提供此服务。

In your ConfigureServices method in your Startup class, you want to use the Options middleware, and inject an object of type VariablesNeeded in to the pipeline.

在Startup类的ConfigureServices方法中,您希望使用Options中间件,并将一个VariablesNeeded类型的对象注入到管道中。

public void ConfigureServices(IServiceCollection services)
{
   // Tells the pipeline we want to use IOption Assessor Services
   services.AddOptions();

   // Injects the object VariablesNeeded in to the pipeline with our desired variables
   services.Configure<VariablesNeeded>(x =>
   {
       x.Foo1 = Configuration["KeyInAppSettings"]
       x.Foo2 = Convert.ToInt32(Configuration["KeyParentName:KeyInAppSettings"])
   });

   //You may want another set of options for another controller, or perhaps to pass both to our "MyController" if so, you just add it to the pipeline    
   services.Configure<OtherVariablesNeeded>(x =>
   {
       x.Foo1 = "Other Test String",
       x.Foo2 = 2
   });

   //The rest of your configure services...
}

For more information see the chapter on Using Options and configuration objects in the ASPCore Docs

有关更多信息,请参阅ASPCore文档中有关使用选项和配置对象的章节

#1


11  

You are entangling 2 different run time resource provider, AppSettings and Dependency Injection.

您正在纠缠2个不同的运行时资源提供程序,AppSettings和依赖注入。

AppSettings, provides run-time access to Application specific values like UICulture strings, Contact Email, etc.

AppSettings,提供对特定于应用程序的值的运行时访问,如UICulture字符串,联系电子邮件等。

DI Containers are factories that Manage access to Services and their lifetime scopes. For example, If a MVC Controller needed access to your EmailService, you would configure

DI容器是管理服务访问及其生命周期范围的工厂。例如,如果MVC Controller需要访问您的EmailService,您将进行配置

   public void ConfigureServices(IServiceCollection services)
   {
      // Add all dependencies needed by Mvc.
      services.AddMvc();

      // Add EmailService to the collection. When an instance is needed,
      // the framework injects this instance to the objects that needs it
      services.AddSingleton<IEmailService, EmailService>();
   }

Then, if our Home Controller needs access to your EmailService, we add a dependency on it's Interface by adding it as a parameter to the Controller constructor

然后,如果我们的Home Controller需要访问您的EmailService,我们通过将它作为参数添加到Controller构造函数来添加对它的接口的依赖性

public class HomeController : Controller
{
   private readonly IEmailService _emailService;
   private readonly string _emailContact;

  /// The framework will inject an instance of an IEmailService implementation.
   public HomeController(IEmailService emailService)
   {
      _emailService = emailService;
      _emailContact = System.Configuration.ConfigurationManager.
                   AppSettings.Get("ContactEmail");
   }

   [HttpPost]
   public void EmailSupport([FromBody] string message)
   {
      if (!ModelState.IsValid)
      {
         Context.Response.StatusCode = 400;
      }
      else
      {
         _emailService.Send(_emailContact, message);

The purpose of Dependancy Injection is to manage access and lifetimes of services.

Dependancy Injection的目的是管理服务的访问和生命周期。

In the previous example, in our Application Startup, we configured the DI Factory to associate application requests for IEmailService with EmailService. So when our Controllers are instantiate by the MVC Framework, the framework notices that our Home Controller expects IEmailService, the framework checks our Application Services Collection. It finds mapping instructions and Inject a Singleton EmailService (a descendant of the occupying Interface) into our Home Controller.

在前面的示例中,在我们的应用程序启动中,我们将DI Factory配置为将IEmailService的应用程序请求与EmailService相关联。因此,当我们的控制器由MVC框架实例化时,框架会注意到我们的Home Controller需要IEmailService,框架会检查我们的Application Services Collection。它找到映射指令并将Singleton EmailService(占用接口的后代)注入我们的Home Controller。

Super Polymorphic Factorific - alodocious!

超级多态因子 - 太过分了!

Why is this important?

为什么这很重要?

If your contact email changes, you change the AppSetting value and are done. All requests for "ContactEmail" from ConfigurationManager are Globally changed. Strings are easy. No need for Injection when we can just hash.

如果您的联系电子邮件发生更改,则更改AppSetting值并完成。 ConfigurationManager中对“ContactEmail”的所有请求都是全局更改的。字符串很简单。我们可以哈希时不需要注入。

If your Repository, Email Service, Logging Service, etc changes, you want a Global way to change all references to this service. Service reference aren't as easily transferred as immutable string literals. Service instantiation should be handled by a factory to configure the Service's settings and dependencies.

如果您的存储库,电子邮件服务,日志记录服务等发生更改,您需要一种全局方式来更改对此服务的所有引用。服务引用不像不可变字符串文字那样容易转移。服务实例化应由工厂处理,以配置服务的设置和依赖关系。

So, in a year you develop a RobustMailService:

所以,在一年内你开发了一个RobustMailService:

Class RobustMailService : IEmailService
{

....

}

As long as your new RobustMailService inherits and implements the IEmailService Interface, you can substitute all references to your mail service Globally by changing :

只要您的新RobustMailService继承并实现了IEmailService接口,您就可以通过更改以下内容替换所有对您的邮件服务的引用:

   public void ConfigureServices(IServiceCollection services)
   {
      // Add all dependencies needed by Mvc.
      services.AddMvc();

      // Add RobustMailService to the collection. When an instance is needed,
      // the framework injects this instance to the objects that needs it 
      services.AddSingleton<IEmailService, RobustMailService>();
   }

#2


3  

This can be achieved using IOptions assessor service as it seems you were trying.

这可以使用IOptions评估服务来实现,因为您似乎正在尝试。

We can begin by creating a class with all of the variables that your controller needs from configuration.

我们可以从创建一个包含控制器所需的所有变量的类开始。

public class VariablesNeeded
{
    public string Foo1{ get; set; }        
    public int Foo2{ get; set; }
}

public class OtherVariablesNeeded
{
    public string Foo1{ get; set; }        
    public int Foo2{ get; set; }
}

We now need to tell the middleware that the controller needs this class in the constructor of the controller using dependency injection, we do this using IOptions accessor service.

我们现在需要告诉中间件控制器需要使用依赖注入在控制器的构造函数中使用此类,我们使用IOptions访问器服务。

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

public class MyController: Controller{    
    private readonly VariablesNeeded _variablesNeeded;

    public MyController(IOptions<VariablesNeeded> variablesNeeded) {
        _variablesNeeded= variablesNeeded.Value;
    }

    public ActionResult TestVariables() {
        return Content(_variablesNeeded.Foo1 + _variablesNeeded.Foo2);
    }
}

To get the variables from your configuration files, we create a constructor for the startup class, and a configuration property.

要从配置文件中获取变量,我们为启动类和配置属性创建构造函数。

public IConfigurationRoot Configuration { get; }

public Startup(IHostingEnvironment env)
{
    /* This is the fairly standard procedure now for configuration builders which will pull from appsettings (potentially with an environmental suffix), and environment variables. */
    var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)    
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
    Configuration = builder.Build();
}

Now we need to make sure the pipeline actually supplies the controller with this service.

现在我们需要确保管道实际上为控制器提供此服务。

In your ConfigureServices method in your Startup class, you want to use the Options middleware, and inject an object of type VariablesNeeded in to the pipeline.

在Startup类的ConfigureServices方法中,您希望使用Options中间件,并将一个VariablesNeeded类型的对象注入到管道中。

public void ConfigureServices(IServiceCollection services)
{
   // Tells the pipeline we want to use IOption Assessor Services
   services.AddOptions();

   // Injects the object VariablesNeeded in to the pipeline with our desired variables
   services.Configure<VariablesNeeded>(x =>
   {
       x.Foo1 = Configuration["KeyInAppSettings"]
       x.Foo2 = Convert.ToInt32(Configuration["KeyParentName:KeyInAppSettings"])
   });

   //You may want another set of options for another controller, or perhaps to pass both to our "MyController" if so, you just add it to the pipeline    
   services.Configure<OtherVariablesNeeded>(x =>
   {
       x.Foo1 = "Other Test String",
       x.Foo2 = 2
   });

   //The rest of your configure services...
}

For more information see the chapter on Using Options and configuration objects in the ASPCore Docs

有关更多信息,请参阅ASPCore文档中有关使用选项和配置对象的章节