Множественная регистрация сервисов

Последнее обновление: 05.02.2022

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

Регистрация для одной зависимости нескольких типов

ASP.NET Core позволяет зарегистрировать для одной зависимости сразу несколько типов. Рассмотрим простейший пример:

var builder = WebApplication.CreateBuilder();
builder.Services.AddTransient<IHelloService, RuHelloService>();
builder.Services.AddTransient<IHelloService, EnHelloService>();

var app = builder.Build();

app.UseMiddleware<HelloMiddleware>();

app.Run();


interface IHelloService
{
    string Message { get; }
}

class RuHelloService : IHelloService
{
    public string Message => "Привет METANIT.COM";
}
class EnHelloService : IHelloService
{
    public string Message => "Hello METANIT.COM";
}

class HelloMiddleware
{
    readonly IEnumerable<IHelloService> helloServices;

    public HelloMiddleware(RequestDelegate _, IEnumerable<IHelloService> helloServices)
    {
        this.helloServices = helloServices;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        context.Response.ContentType = "text/html; charset=utf-8";
        string responseText = "";
        foreach (var service in helloServices)
        {
            responseText += $"<h3>{service.Message}</h3>";
        }
        await context.Response.WriteAsync(responseText);
    }
}

Здесь интерфейс IHelloService с помощью свойства Message определяет некоторое сообщение. Этот интерфейс реализуется двумя классами: RuHelloService и EnHelloService, каждый из которых определяет свое сообщение. И оба этих класса регистрируются в коллекции сервисов в качестве реализаций для сервиса IHelloService. Далее все эти реализации можно получить в виде коллекции IEnumerable<IHelloService>.

Этот сервис применяется в middleware HelloMiddleware, который внедряется в конвейер обработки запроса и обрабатывает все запросы к приложению. И в его конструкторе получаем все зарегистрированные реализации сервиса IHelloService через объект IEnumerable<IHelloService>

public HelloMiddleware(RequestDelegate _, IEnumerable<IHelloService> helloServices)
{
    this.helloServices = helloServices;
}

Соответственно далее все зарегистрированные реализации сервиса IHelloService можно использовать для обработки запроса:

Множественная регистрация сервисов в ASP.NET Core и C#

Регистрация одного объекта для нескольких зависимостей

Теперь рассмотрим другую ситуацию: использование несколькими зависимостями одного и то же объекта. Сначала рассмотрим ситуацию, с которой мы можем столкнуться. Допустим, у нас есть следующие объекты:

interface IGenerator
{
    int GenerateValue();
}
interface IReader
{
    int ReadValue();
}
class ValueStorage : IGenerator, IReader
{
    int value;
    public int GenerateValue()
    {
        value = new Random().Next();
        return value;
    }

    public int ReadValue() => value;
}

Здесь интерфейс IGenerator предназначен для генерации некоторого числа, а интерфейс IReader - для чтения некоторого числа. Класс ValueStorage реализует оба этих интерфейса: в методе Generate изменяет значение переменной value и возвращает его, а в методе Read просто возвращает значение переменной value. То есть метод Read считывает текущее значение value, а метод Generate изменяет его.

Используем эти сервисы в приложении:

var builder = WebApplication.CreateBuilder();
builder.Services.AddSingleton<IGenerator, ValueStorage>();
builder.Services.AddSingleton<IReader, ValueStorage>();

var app = builder.Build();

app.UseMiddleware<GeneratorMiddleware>();
app.UseMiddleware<ReaderMiddleware>();

app.Run();

class GeneratorMiddleware
{
    RequestDelegate next;
    IGenerator generator;

    public GeneratorMiddleware(RequestDelegate next, IGenerator generator)
    {
        this.next = next;
        this.generator = generator;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Path == "/generate")
            await context.Response.WriteAsync($"New Value: {generator.GenerateValue()}");
        else
            await next.Invoke(context);
    }
}
class ReaderMiddleware
{
    IReader reader;

    public ReaderMiddleware(RequestDelegate _, IReader reader) => this.reader = reader;
    
    public async Task InvokeAsync(HttpContext context)
    {
        await context.Response.WriteAsync($"Current Value: {reader.ReadValue()}");
    }
}

interface IGenerator
{
    int GenerateValue();
}
interface IReader
{
    int ReadValue();
}
class ValueStorage : IGenerator, IReader
{
    int value;
    public int GenerateValue()
    {
        value = new Random().Next();
        return value;
    }

    public int ReadValue() => value;
}

Здесь для обоих зависимостей - IGenerator и IReader определена одна реализация - ValueStorage. При обращении по адресу "/generate" срабатывает middleware GenerateMiddleware, который получает сервис IGenerator и с его помощью генерирует новое значение.

При обращении по всем иным адресам срабатывает middleware ReaderMiddleware, который получает сервис IReader и возвращает текущее значение. Однако при запуске проекта мы увидим, что генерируемое значение и возвращаемое значения никак не синхронизированы, потому что, несмотря на то, что оба сервиса представляют синглтоны, они используют два разных экземпляра класса ValueStorage:

регистрация для нескольких зависимостей одного объекта в ASP.NET Core и C#

Для исправления ситуации нам надо определить один объект для обоих зависимостей. Это можно сделать, например, следующим образом:

var builder = WebApplication.CreateBuilder();

var valueStorage = new ValueStorage();
builder.Services.AddSingleton<IGenerator>(valueStorage);
builder.Services.AddSingleton<IReader>(valueStorage);

var app = builder.Build();

app.UseMiddleware<GeneratorMiddleware>();
app.UseMiddleware<ReaderMiddleware>();

app.Run();

class GeneratorMiddleware
{
    RequestDelegate next;
    IGenerator generator;

    public GeneratorMiddleware(RequestDelegate next, IGenerator generator)
    {
        this.next = next;
        this.generator = generator;
    }
    public async Task InvokeAsync(HttpContext context)
    {
        if (context.Request.Path == "/generate")
            await context.Response.WriteAsync($"New Value: {generator.GenerateValue()}");
        else
            await next.Invoke(context);
    }
}
class ReaderMiddleware
{
    IReader reader;

    public ReaderMiddleware(RequestDelegate _, IReader reader) => this.reader = reader;
    
    public async Task InvokeAsync(HttpContext context)
    {
        await context.Response.WriteAsync($"Current Value: {reader.ReadValue()}");
    }
}

interface IGenerator
{
    int GenerateValue();
}
interface IReader
{
    int ReadValue();
}
class ValueStorage : IGenerator, IReader
{
    int value;
    public int GenerateValue()
    {
        value = new Random().Next();
        return value;
    }

    public int ReadValue() => value;
}

Теперь определяем один объект ValueStorage:

var valueStorage = new ValueStorage();

Затем передаем его в сервисы:

builder.Services.AddSingleton<IGenerator>(valueStorage);
builder.Services.AddSingleton<IReader>(valueStorage);

Соответственно теперь мы получим другие результаты:

один сервис для нескольких зависимостей в ASP.NET Core и C#

В качестве альтернативы можно было бы определить ValueStorage как сервис-синглтон и затем при регистрации других зависимостей получить этот сервис:

var builder = WebApplication.CreateBuilder();

builder.Services.AddSingleton<ValueStorage>();
builder.Services.AddSingleton<IGenerator>(serv => serv.GetRequiredService<ValueStorage>());
builder.Services.AddSingleton<IReader>(serv => serv.GetRequiredService<ValueStorage>());
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850