Фреймворк ASP.NET Core позволяет получить все добавленные в приложение сервисы различными способами:
Через свойство Services объекта WebApplication
Через свойство RequestServices контекста запроса HttpContext в компонентах middleware
Через конструктор класса
Через параметр метода Invoke компонента middleware
Через свойство Services объекта WebApplicationBuilder
Для рассмотрения всех этих способов возьмем интерфейс ITimeService и класс ShortTimeService, который реализует данный интерфейс:
Interface ITimeService Function GetTime() As String End Interface Class ShortTimeService Implements ITimeService Public Function ITimeService_GetTime() As String Implements ITimeService.GetTime Return Now.ToShortTimeString() End Function End Class
Там, где нам доступен объект WebApplication, который представляет текущее приложение, (например, в файле Program.vb), для получения сервисов мы можем использовать его свойство Services. Это свойство предоставляет объект IServiceProvider, который предоставляет ряд методов для получения сервисов:
GetService(Of service)(): использует провайдер сервисов для создания объекта, который представляет тип service. В случае если в провайдере сервисов для данного сервиса не установлена зависимость, то возвращает значение null
GetRequiredService(Of service)(): использует провайдер сервисов для создания объекта, который представляет тип service. В случае если в провайдере сервисов для данного сервиса не установлена зависимость, то генерирует исключение
Данный паттерн получения сервиса еще называется service locator, и, как правило, не рекомендуется к использованию, но тем не менее в рамках ASP.NET Core в принципе мы можем использовать подобную функциональность особенно там, где другие способы получения зависимостей не достуны.
Например, определим в файле Program.vb следующий код приложения:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Imports Microsoft.Extensions.DependencyInjection Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) 'добавляем сервис в приложение builder.Services.AddTransient(Of ITimeService, ShortTimeService) Dim app = builder.Build() app.Run(Async Function(context As HttpContext) As Task 'получаем сервис Dim timeService = app.Services.GetService(Of ITimeService)() Await context.Response.WriteAsync($"Time: {timeService?.GetTime}") End Function) app.Run() End Sub Interface ITimeService Function GetTime() As String End Interface Class ShortTimeService Implements ITimeService Public Function ITimeService_GetTime() As String Implements ITimeService.GetTime Return Now.ToShortTimeString() End Function End Class End Module
В данном случае с помощью строки кода
Dim timeService = app.Services.GetService(Of ITimeService)();
Получаем из коллекции сервисов объект сервиса ITimeService
- в данном случае он будет представлять объект ShortTimeService
Возможна ситуация, когда сервис не будет добавлен в коллекцию сервисов, однако в какой-то части приложения мы может попытаться его получить:
Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) 'builder.Services.AddTransient(Of ITimeService, ShortTimeService) Dim app = builder.Build() app.Run(Async Function(context As HttpContext) As Task Dim timeService = app.Services.GetService(Of ITimeService)() Await context.Response.WriteAsync($"Time: {timeService?.GetTime}") End Function) app.Run() End Sub
В этом случае переменная timeService
будет иметь значение Nothing.
Аналогичный образом можно использовать метод GetRequiredService() за тем исключением, что если сервис не добавлен, то метод генерирует исключение:
Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) 'builder.Services.AddTransient(Of ITimeService, ShortTimeService) Dim app = builder.Build() app.Run(Async Function(context As HttpContext) As Task Dim timeService = app.Services.GetRequiredService(Of ITimeService)() Await context.Response.WriteAsync($"Time: {timeService?.GetTime}") End Function) app.Run() End Sub
Там, где нам доступен объект HttpContext, мы можем использовать для получения сервисов его свойство RequestServices. Это свойство предоставляет объект IServiceProvider. То есть по сути мы имеем дело с выше описанным способом получения сервисов с помощью методов GetService() и GetRequiredService():
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Imports Microsoft.Extensions.DependencyInjection Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) builder.Services.AddTransient(Of ITimeService, ShortTimeService) Dim app = builder.Build() app.Run(Async Function(context As HttpContext) As Task Dim timeService = context.RequestServices.GetService(Of ITimeService)() Await context.Response.WriteAsync($"Time: {timeService?.GetTime}") End Function) app.Run() End Sub Interface ITimeService Function GetTime() As String End Interface Class ShortTimeService Implements ITimeService Public Function ITimeService_GetTime() As String Implements ITimeService.GetTime Return Now.ToShortTimeString() End Function End Class End Module
Встроенная в ASP.NET Core система внедрения зависимостей позволяет получить сервисы через конструкторы классов. Передача сервисов через конструкторы является предпочтительным способом внедрения зависимостей.
Например, изменим код файла следующим образом:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Imports Microsoft.Extensions.DependencyInjection Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) builder.Services.AddTransient(Of ITimeService, ShortTimeService) builder.Services.AddTransient(Of TimeMessage) Dim app = builder.Build() app.Run(Async Function(context As HttpContext) As Task Dim timeMessenger = context.RequestServices.GetService(Of TimeMessage)() context.Response.ContentType = "text/html;charset=utf-8" Await context.Response.WriteAsync($"<h2>{timeMessenger?.GetTime()}</h2>") End Function) app.Run() End Sub Class TimeMessage Dim timeService As ITimeService Public Sub New(timeService As ITimeService) Me.timeService = timeService End Sub Public Function GetTime() As String Return $"Time: {timeService.GetTime()}" End Function End Class Interface ITimeService Function GetTime() As String End Interface Class ShortTimeService Implements ITimeService Public Function ITimeService_GetTime() As String Implements ITimeService.GetTime Return Now.ToShortTimeString() End Function End Class End Module
Здесь определен класс TimeMessage, который через конструктор получает зависимость от ITimeService:
Class TimeMessage Dim timeService As ITimeService Public Sub New(timeService As ITimeService) Me.timeService = timeService End Sub Public Function GetTime() As String Return $"Time: {timeService.GetTime()}" End Function End Class
Причем на момент создания класса неизвестно, что это будет за реализация интерфейса ITimeService.
В методе GetTime()
формируем сообщение, в котором из сервиса получаем текущее время.
Для использования в приложении в качестве сервиса класс MessageService также добавляется в коллекцию сервисов.
Поскольку это самодостаточная зависимость, которая представляет
конкретный класс, то метод builder.Services.AddTransient
типизируется одним этим типом TimeMessage. То есть классы, которые используют сервисы, сами
могут выступать в качестве сервисов.
builder.Services.AddTransient(Of TimeMessage)()
Но так как класс TimeMessage использует зависимость ITimeService, которая передается через конструктор, то нам надо также установить и эту зависимость:
builder.Services.AddTransient(Of ITimeService, ShortTimeService)()
И когда при обработке запроса будет использоваться класс TimeMessage, для создания объекта этого класса будет вызываться провайдер сервисов. Провайдер сервисов проверят конструктор класса TimeMessage на наличие зависимостей. Затем создает объекты для всех используемых зависимостей и передает их в конструктор.
Подобно тому, как зависимости передаются в конструктор классов, точно также их можно передавать в метод Invoke/InvokeAsync() компонента middleware. Например,определим следующий компонент:
Imports Microsoft.AspNetCore.Builder Imports Microsoft.AspNetCore.Http Imports Microsoft.Extensions.DependencyInjection Module Program Sub Main(args As String()) Dim builder = WebApplication.CreateBuilder(args) builder.Services.AddTransient(Of ITimeService, ShortTimeService) Dim app = builder.Build() app.UseMiddleware(Of TimeMessageMiddleware) app.Run() End Sub Class TimeMessageMiddleware Public Sub New(nextMiddleware As RequestDelegate) End Sub Public Async Function InvokeAsync(context As HttpContext, timeService As ITimeService) As Task context.Response.ContentType = "text/html;charset=utf-8" Await context.Response.WriteAsync($"<h1>Time: {timeService.GetTime()}</h1>") End Function End Class Interface ITimeService Function GetTime() As String End Interface Class ShortTimeService Implements ITimeService Public Function ITimeService_GetTime() As String Implements ITimeService.GetTime Return Now.ToShortTimeString() End Function End Class End Module
Здесь определен класс-middleware TimeMessageMiddleware, который в методе InvolkeAsync получает сервис ITiemService и использует его:
Class TimeMessageMiddleware Public Sub New(nextMiddleware As RequestDelegate) End Sub Public Async Function InvokeAsync(context As HttpContext, timeService As ITimeService) As Task context.Response.ContentType = "text/html;charset=utf-8" Await context.Response.WriteAsync($"<h1>Time: {timeService.GetTime()}</h1>") End Function End Class
Стоит отметить, что мы также могли бы передать зависимость и через конструктор класса middleware:
Class TimeMessageMiddleware Dim timeService As ITimeService Public Sub New(nextMiddleware As RequestDelegate, timeService As ITimeService) Me.timeService = timeService End Sub Public Async Function InvokeAsync(context As HttpContext) As Task context.Response.ContentType = "text/html;charset=utf-8" Await context.Response.WriteAsync($"Time: {timeService.GetTime()}
") End Function End Class
Благодаря подобной передаче зависимости мы можем уйти от использования паттерна "service locator", который демонстрировался в предыдущих примерах.