Получение значения конфигурации в ASP.NET 5 (vNext)

Я борюсь с некоторыми понятиями в ASP.NET 5 (vNext).

Один из них - это подход Подход к зависимостям, используемый для конфигурации. Похоже, что я должен пройти параметр через стек. Возможно, я что-то недопонимаю или делаю неправильно.

Представьте, что у меня есть свойство config с именем "contactEmailAddress". Я буду использовать это свойство config для отправки электронной почты при размещении нового заказа. Учитывая этот сценарий, мой стек 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":"[email protected]"  
  }
}

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 через весь стек. Это не кажется правильным. Чтобы я больше не беспокоился, этот подход не будет работать, если я попытаюсь использовать стороннюю библиотеку, которая должна получить доступ к настройкам конфигурации. Как сторонняя библиотека может получить доступ к настройкам конфигурации? Я что-то не понимаю? Есть ли лучший способ сделать это?

Ответ 1

Вы запутываете 2 разных поставщика ресурсов времени выполнения, AppSettings и Инъекции зависимостей.

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

Затем, если нашему домашнему контроллеру нужен доступ к вашему EmailService, мы добавим зависимость от него Интерфейс, добавив его как параметр в конструктор контроллера

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

Цель Injection Dependancy - управлять доступом и сроками службы.

В предыдущем примере в нашем приложении Startup мы настроили DI Factory, чтобы связать запросы приложений для IEmailService с EmailService. Поэтому, когда наши Контроллеры создаются с помощью MVC Framework, структура отмечает, что наш Home Controller ожидает IEmailService, фреймворк проверяет нашу коллекцию приложений. Он находит инструкции отображения и Inject a Singleton EmailService (потомок оккупирующего интерфейса) в наш домашний контроллер.

Суперполиморфный фактор - алодозный!

Почему это важно?

Если ваше контактное письмо изменилось, вы измените значение AppSetting и закончите. Все запросы для "ContactEmail" из ConfigurationManager глобально изменены. Строки просты. Нет необходимости в инъекции, когда мы можем просто хешировать.

Если ваш репозиторий, служба электронной почты, служба ведения журналов и т.д. изменяется, вам нужен глобальный способ изменить все ссылки на эту службу. Ссылка на службу не так легко передается как непреложные строковые литералы. Консоль службы должна обрабатываться Factory для настройки параметров службы и зависимостей.

Итак, через год вы разработаете 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>();
   }

Ответ 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();
}

Теперь нам нужно убедиться, что трубопровод действительно снабжает контроллер этой услугой.

В вашем методе ConfigureServices в вашем классе Startup вы хотите использовать промежуточное программное обеспечение 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