【问题标题】:Getting a Configuration Value in ASP.NET 5 (vNext)在 ASP.NET 5 (vNext) 中获取配置值
【发布时间】:2015-05-25 17:17:29
【问题描述】:

我正在为 ASP.NET 5 (vNext) 中的一些概念而苦苦挣扎。

其中之一是用于配置的依赖注入方法。似乎我必须一直通过堆栈传递一个参数。我可能误解了什么或做错了。

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

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

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

config.json

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

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

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>();
  }
}

如上例所示,我将AppSettings 传递给整个堆栈。这感觉不正确。更让我担心的是,如果我尝试使用需要访问配置设置的第三方库,这种方法将不起作用。第三方库如何访问配置设置?我是不是误会了什么?有没有更好的方法来做到这一点?

【问题讨论】:

  • 这只是一个电子邮件地址还是多个地址?

标签: c# asp.net asp.net-mvc asp.net-core asp.net-core-mvc


【解决方案1】:

您正在纠缠 2 个不同的运行时资源提供程序,AppSettingsDependency Injection

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

DI 容器 是管理对服务及其生命周期范围的访问的工厂。例如,如果一个 MVC 控制器 需要访问您的 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>();
   }

然后,如果我们的 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);

依赖注入的目的是管理服务的访问和生命周期

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

超级多态因子 - alodocious!

为什么这很重要?

如果您的联系电子邮件更改,您更改 AppSetting 值并完成。来自ConfigurationManager 的所有“ContactEmail”请求都已全局更改。字符串很容易。当我们可以哈希时不需要注入。

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

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

Class RobustMailService : IEmailService
{

....

}

只要您的新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>();
   }

【讨论】:

  • 虽然你的答案非常详细@DavidMoores 答案实际上是正确的,因为他指定了如何将配置传递给从 Startup.cs 一路需要它们的服务
  • @Drakoumel,我真的不知道你想让我说什么。我不同意,另外 12 个人不同意你的评估。此外,我强调不回应对我的答案的含糊批评。你没有做出详细的评论,我不能确定你是否理解了我的问题或我的回答。
  • 我不是故意冒犯你,我评论是因为我和 OP 有同样的问题,看来你的答案已经过时了。为什么?正如我所说,大卫的解决方案解释了如何设置 IOptions 模型以便将配置文件的特定块传递到服务中。你完全忽略了那部分。您指定如何定义正确的 MVC 和服务层,但没有像 David 那样指定如何创建设置模型。
  • 老实说,我不会继续这次谈话,因为你似乎无缘无故地咄咄逼人,而且我对此无能为力。祝你有美好的一天。
  • @DaveAlperovich 你能澄清一下吗?在这个答案中,您非常清楚在使用服务的位置应用配置(在本例中为电子邮件) - 这是有道理的。您将如何应用初始化服务 所需的设置?那么在这个电子邮件案例中,SMTP 服务器之类的?显然在appsettings 可以轻松更改它们,但是应该在哪里读取这些设置? RobustMailService 构造函数?这对我来说似乎是错误的地方,Startup.ConfigureServices 更有意义,但你不是将其描述为对 DI 的滥用吗?
【解决方案2】:

这可以使用 IOptions 评估服务来实现,就像您正在尝试的那样。

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

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; }
}

我们现在需要在控制器的构造函数中使用依赖注入告诉中间件控制器需要这个类,我们使用 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);
    }
}

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

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();
}

现在我们需要确保管道确实为控制器提供了这项服务。

在 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...
}

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

【讨论】:

    猜你喜欢
    • 2015-07-27
    • 1970-01-01
    • 2016-04-09
    • 1970-01-01
    • 2015-06-26
    • 1970-01-01
    • 2015-10-16
    • 2015-10-12
    相关资源
    最近更新 更多