Передача зависимостей

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 07.11.2019
В ASP.NET Core мы можем получить добавленные в приложения сервисы различными способами;

  • Через конструктор класса (за исключением конструктора класса Startup)

  • Через параметр метода Configure класса Startup

  • Через параметр метода Invoke компонента middleware

  • Через свойство RequestServices контекста запроса HttpContext в компонентах middleware (service locator)

  • Через свойство ApplicationServices объекта IApplicationBuilder в классе Startup

Для работы возьмем проект ASP.NET Core по типу Empty и определим в нем типы IMessageSender и EmailMessageSender:

public interface IMessageSender
{
	string Send();
}
public class EmailMessageSender : IMessageSender
{
	public string Send()
	{
		return "Sent by Email";
	}
}

Параметр метода Configure

Метод Configure класса Startup позволяет напрямую получать зависимость в качестве параметра метода. Например:

public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddTransient<IMessageSender, EmailMessageSender>();
	}
	// Получаем зависимость IMessageSender
	public void Configure(IApplicationBuilder app, IMessageSender sender)
	{
		app.Run(async (context) =>
		{
			await context.Response.WriteAsync(sender.Send());
		});
	}
}

Данный способ естественно может применяться только в классе Startup.

Конструкторы

Встроенная в ASP.NET Core система внедрения зависимостей использует конструкторы классов для передачи всех зависимостей. Соответственно в конструкторе контроллера мы можем получить зависимость. Конструкторы являются наиболее предпочтительным вариантом для получения зависимостей.

Например, пусть в проекте определен класс MessageService:

public class MessageService
{
	IMessageSender _sender;
	public MessageService(IMessageSender sender)
	{
		_sender = sender;
	}
	public string Send()
	{
		return _sender.Send();
	}
}

Здесь через конструктор класса передается зависимость от IMessageSender. Причем здесь неизвестно, что это будет за реализация интерфейса IMessageSender.

Изменим класс Startup для использования MessageService:

public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddTransient<IMessageSender, EmailMessageSender>();
		services.AddTransient<MessageService>();
	}

	public void Configure(IApplicationBuilder app, MessageService messageService)
	{
		app.Run(async (context) =>
		{
			await context.Response.WriteAsync(messageService.Send());
		});
	}
}

Чтобы использовать класс MessageService, он внедряется в приложение в виде сервиса. Поскольку это самодостаточная зависимость, которая представляет конкретный класс, то метод services.AddTransient типизируется одним этим типом MessageService. То есть классы, использующие сервисы, сами могут выступать в качестве сервисов.

Но так как класс MessageService использует зависимость IMessageSender, которая передается через конструктор, то нам надо также установить и эту зависимость:

services.AddTransient<IMessageSender, EmailMessageSender>();

И когда при обработке запроса будет использоваться класс MessageService, для создания объекта этого класса будет вызываться провайдер сервисов. Провайдер сервисов проверят конструктор класса MessageService на наличие зависимостей. Затем создает объекты для всех используемых зависимостей и передает их в конструктор.

В методе Configure сервис MessageService передается в качестве параметра и участвует в обработке запроса.

А результатом работы приложения будет вывод строки:

Тестирование внедрения зависимостей в ASP.NET Core

Данный способ может применяться для передачи зависимостей в конструктор любого класса кроме класса Startup.

HttpContext.RequestServices

Объект HttpContext.RequestServices предоставляет доступ к всем внедренным зависимостям с помощью своих методов:

  • GetService<service>(): использует провайдер сервисов для создания объекта, который представляет тип service. В случае если в провайдере сервисов для данного сервиса не установлена зависимость, то возвращает значение null

  • GetRequiredService<service>(): использует провайдер сервисов для создания объекта, который представляет тип service. В случае если в провайдере сервисов для данного сервиса не установлена зависимость, то генерирует исключение

Данный паттерн получения сервиса еще называется service locator, и, как правило, не рекомендуется к использованию, но тем не менее в рамках ASP.NET Core в принципе мы можем использовать подобную функциональность.

Например, изменим класс Startup:

public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddTransient<IMessageSender, EmailMessageSender>();
	}
	public void Configure(IApplicationBuilder app)
    {
		app.Run(async (context) =>
		{
			IMessageSender messageSender = context.RequestServices.GetService<IMessageSender>();
			context.Response.ContentType = "text/html;charset=utf-8";
			await context.Response.WriteAsync(messageSender.Send());
		});
	}
}

ApplicationServices

Еще один похожий способ представляет получение сервисов через свойство ApplicationServices объекта IApplicationBuilder, который передается в качестве параметра в метод Configure класса Startup:

public void Configure(IApplicationBuilder app)
{
	app.Run(async (context) =>
	{
		IMessageSender messageSender = app.ApplicationServices.GetService<IMessageSender>();
		context.Response.ContentType = "text/html;charset=utf-8";
		await context.Response.WriteAsync(messageSender.Send());
	});
}

Метод Invoke/InvokeAsync компонентов middleware

Подобно тому, как зависимости передаются в метод Configure в классе Startup, точно также их можно передавать в метод Invoke компонента middleware. Например,определим следующий компонент:

using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;

public class MessageMiddleware
{
	private readonly RequestDelegate _next;

	public MessageMiddleware(RequestDelegate next)
	{
		this._next = next;
	}

	public async Task InvokeAsync(HttpContext context, IMessageSender messageSender)
	{
		context.Response.ContentType = "text/html;charset=utf-8";
		await context.Response.WriteAsync(messageSender.Send());
	}
}

Применим компонент для обработки запроса в классе Startup:

public class Startup
{
	public void ConfigureServices(IServiceCollection services)
	{
		services.AddTransient<IMessageSender, EmailMessageSender>();
	}
	public void Configure(IApplicationBuilder app)
	{
		app.UseMiddleware<MessageMiddleware>();
	}
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850