пятница, 28 октября 2016 г.

Введение в ASP.NET Core Dependency Injection



Если вы разрабатывали профессиональные Web приложения с использованием ASP.NET MVC, то вы вероятно уже знакомы с Dependency Injection (DI). Это техника для разработки слабо связанных программных компонентов. ASP.NET MVC не содержит никакого встроенного DF-фреймворка и поэтому разработчикам приходится привлекать сторонние библиотеки. К счастью в ASP.NET Core 1.0 появился DI-контейнер, который может сделать вашу разработку проще. Данная статья описывает базовые возможности DI ASP.NET Core 1.0, использовать которые вы можете в своих приложниях.

Данная статья базируется на RC2 ASP.NET Coer 1.0. Поэтому вам нужно установить данную версию если вы хотите попробовать использовать примеры  из данной статьи. Для установки и подробностей перейдите на сайт Dependency Injection in ASP.NET Core 1.0.

Для того что бы понять как Dependency Injection работает мы создадим простой пример. Для начала создаем ASP.NET Core 1.0 Web Application и используем пустой шаблон.

После этого откроем файл Project.json и добавим зависимости как описано в листинге ниже:

"dependencies": {
   "Microsoft.NETCore.App": {
      "version": "1.0.0-rc2-3002702",
      "type": "platform"
   },

   
   "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
   "Microsoft.AspNetCore.Razor.Tools": {
      "version": "1.0.0-preview1-final",
      "type": "build"
   },
   "Microsoft.AspNetCore.Server.IISIntegration": "1.0.0-rc2-final",
   "Microsoft.AspNetCore.Server.Kestrel": "1.0.0-rc2-final",
   "Microsoft.AspNetCore.StaticFiles": "1.0.0-rc2-final",


   "Microsoft.EntityFrameworkCore.SqlServer": "1.0.0-rc2-final",
   "Microsoft.EntityFrameworkCore.Tools": {
      "version": "1.0.0-preview1-final",
      "type": "build"
   },


   "Microsoft.Extensions.Configuration.EnvironmentVariables":
      "1.0.0-rc2-final",
   "Microsoft.Extensions.Configuration.Json": "1.0.0-rc2-final",


   "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
      "version": "1.0.0-preview1-final",
      "type": "build"
   },
   "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": {
      "version": "1.0.0-preview1-final",
      "type": "build"
   }
}


Не забудьте восстановить пакеты установки, для этого выберите по правому клику на References пункт меню Restore Packages.

После этого создайте папку DIClasses в корне проекта. Добавьте новый интерфейс IServiceType. Этот тип будет испоьзоваться как сервисный тип. Интерфейс будет реализован другим классом, который вы создадите позже. Структура интерфейса представлена ниже.

public interface IServiceType
{
   string GetGuid();
}
 

Интерфейс IServiceType содержит один метод – GetGuid(). Как можно предположить из названия, реализация метода предполагает возврат GUID. В реальном проекте у вас здесь будет логика специфическая для вашего проекта.

После этого добавьте класс MyServiceType в папку Core и реализуйте интерфейс IServiceType. В результате класс может выглядеть вот так:

public class MyServiceType:IServiceType
{
   private string guid;
 
   public MyServiceType()
   {
      this.guid = Guid.NewGuid().ToString();
   }
 
   public string GetGuid()
    {
      return this.guid;
   }
}
 

Класс MyServiceType реализует интерфейс IServiceType. В классе объявлена приватная переменная –giud- которая содержит GUID. Конструктор генерирует новый экземпляр GUID используя соответствующую структуру и присваивает значение приватной переменной guid. Метод GetGuid() просто возвращает значение этой переменной вызывающей стороне. Таким образом, каждый объект MyServiceType будет иметь уникальный GUID. Это значение будет использоваться для понимания принципов работы библиотеки DI, как мы это увидим позже.

Теперь откроем файл Startup.cs и изменим его как показано ниже:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc();
   services.AddScoped();
}
 
public void Configure(IApplicationBuilder app)
{
   app.UseStaticFiles();
   app.UseMvc(routes =>
   {
      routes.MapRoute(
         name: "default",
         template: "{controller=Home}/
            {action=Index}/{id?}");
   });
}
 

Обратите внимание на выделенную строку. Она содержит регистрацию сервисного типа DI-контейнером ASP.NET Core. Метод AddScoped() – это шаблонный метод, и в нем вы указываете интерфейс для вашего типа (IServiceType) и класс реализации (MyServiceType), который будет использовать в приложении.

Тип который мы зарегистрировали в AddScoped() будет доступен на протяжении всего процесса обработки текущего запроса. Это значит, что с каждым запросом будет создаваться новый экземпляр MyServiceType. Давайте проверим это на деле.

Давайте добавим HomeController и Index в соответствующие папки. После этого изменим код контроллера как показано ниже:

public class HomeController : Controller
{
       
   private IServiceType obj;
 
   public HomeController(IServiceType obj)
   {
      this.obj = obj;
   }
 
   public IActionResult Index()
   {
      ViewBag.Guid = obj.GetGuid();
      return View();
   }
}

Констркуктор HomeController принимает параметр типа IServiseType. Этот параметр будет инстанцирован DI-контейнером. Помните, что для того что бы DI работал корректно, тип должен быть зарегистрирован в контейнере (как было показано выше).

Объект iServiceType, созданный DI-контейнером хранится в приватной переменной – obj – для дальнейшего использования. Метод Index() вызывает GetGuid() объекта MyServiceType и сохраняет его в свойстве ViewBag.Guid. Представление Index просто отображает GUID, как это показано ниже:



<h1>@ViewBag.Guid</h1>

Теперь, если мы запустим приложение, вы можете увидеть экран с GUID (например, 259282a5-e0b7-40cd-9fc4-c2330ce78669).

Если вы обновите страницу браузера, то увидите что каждый раз значение Guid менятеся – на каждый новый запрос создается новый объект с уникальным GUID. Это подтверждает, что наша логика работает как и ожидается.

Помимо метода AddScoped(), который мы рассмотрели, существуют и другие методы, которые можно использовать для контролирования жизни создаваемого объекта – AddTransient() и AddSingleton(). Если сервис зарегистрировать с помощью AddTransient() – то на каждый новый запрос будет создаваться новый объект. Таким образом, если один HTTP запрос запросит сервис дважды, то будет создано два отдельных объекта. Сервис зарегистрированный с помощью AddSingleton будет создан лищь однажды и использоваться для всех запросов. Давайте посмотрим как это работает.

Измените Startup.cs как показано ниже:

public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();
   services.AddTransient();
}
 

В данном случае, вы используете метод AddTransient() для регистрации типа. Теперь изменим код контроллера, что бы он выглядел вот так:

public class HomeController : Controller
{
       
   private IServiceType obj1;
   private IServiceType obj2;
 
   public HomeController(IServiceType obj1,IServiceType obj2)
   {
      this.obj1 = obj1;
      this.obj2 = obj2;
   }
 
   public IActionResult Index()
   {
      ViewBag.Guid1 = obj1.GetGuid();
      ViewBag.Guid2 = obj2.GetGuid();
      return View();
   }
}

В таком случае, HomeController имеет два параметра типа IServiceType. Это сделано для того что бы сэмулировать два запроса для того же типа. Возвращаемые значения GUID для обоих объектов хранятся в ViewBag.Если их отобразить на странице, вы увидите что они разные.

Как видите, значения GUID разные в рамках одного HTTP запроса, это говорит о том, что различные экземпляры объекта создаются для каждой перемнной в контроллере. Если вы посмотрите на страницу в браузере – то увидите что значения меняются кждый раз. Давайте изменим Startup.cs и используем AddScoped() для регистрации. После запуска приложения можно убедится, что показывается один и тот же GUID.

Теперь изменим Startup.cs и будем использовать метод AddSingleton():

public void ConfigureServices(IServiceCollection services)
{
   services.AddMvc();
   services.AddSingleton();
}



После этого сделаем соответствующие изменения в HomeController (здесь будет опять один параметр) и представлении Index. Если вы запустите приложение и обновите браузер, то увидите, что сейчас для все запросов отображается один и тот же GUID, как и требуется для данного режима.