ASP.NET Core Web API. Создание и регистрация сервисов

Чаще всего, в качестве типа сервиса выступает какой-либо интерфейс, однако, это обстоятельство не является обязательным — в качестве сервиса может выступать и обычных класс. Рассмотрим возможные варианты создания сервисов и их регистрации в контейнере DI

Пример сервиса

Рассмотрим пример сервиса который будет генерировать хэш для произвольной строки символов. Для этого создадим новое приложение ASP.NET Core Web API, добавим в проект новую папку Services и разместим в ней файл HashCalc.cs:

using System.Security.Cryptography;
using System.Text;

namespace HashCalculator.Services
{
    public interface IHash
    {
        public string GetHash(string data);
    }

    public class MD5Hash : IHash
    {
        public string GetHash(string data)
        {
            return Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(data)));
        }
    }

    public class SHA1Hash : IHash 
    {
        public string GetHash(string data) 
        {
            return Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(data)));
        }
    }
}

Сервис, представленный типом IHash, может иметь две реализации — MD5Hash и SHA1Hash, каждая из которых возвращает свой тип хэша (MD5 или SHA1). Теперь мы можем воспользоваться этим сервисом, зарегистрировав его в контейнере DI и запросив в каком-либо контроллере. Вначале зарегистрируем сервис, как мы это делали в предыдущей части, то есть в файле Program.cs до строки var app = builder.Build();:

builder.Services.AddSingleton<IHash, MD5Hash>();

Теперь создадим в папке Controllers новый контроллер со следующим содержимым:

using HashCalculator.Services;
using Microsoft.AspNetCore.Mvc;

namespace HashCalculator.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class HashController : Controller
    {
        private readonly IHash _calc;

        public HashController(IHash calc)
        {
            _calc = calc;
        }

        [HttpGet]
        public string Index(string data)
        {
            return _calc.GetHash(data);
        }
    }
}

здесь мы в конструкторе HashController запрашиваем сервис и в методе Index будем возвращать пользователю хэш строки, заданной в параметрах запроса, как параметр data. Теперь запустим приложение и проверим работу:

Если нам потребуется использовать не MD5, а SHA1, то мы можем заменить реализацию:

builder.Services.AddSingleton<IHashCalc, SHA1Hash>();

и в результате наш контроллер будет возвращать тип хэша SHA1. Это распространенная практика использования сервисов в ASP.NET Core, когда в качестве типа сервиса выступает интерфейс. При этом, в качестве типа сервиса может выступать и обычный класс.

Сервис без реализации интерфейса

ASP.NET Core позволяет регистрировать в качестве сервисов обычные классы. К примеру, у нас есть два класса — MD5Hash и SHA1Hash. Зарегистрируем каждый из этих классов как сервис:

builder.Services.AddSingleton<MD5Hash>();
builder.Services.AddSingleton<SHA1Hash>();

И изменим контроллер следующим образом:

[ApiController]
[Route("[controller]")]
public class HashController : Controller
{
    private readonly IHash _md5calc;
    private readonly IHash _sha1calc;

    public HashController(MD5Hash md5calc, SHA1Hash sha1calc)
    {
        _md5calc = md5calc;
        _sha1calc = sha1calc;
    }

    [HttpGet]
    public string[] Index(string data)
    {
        return [_md5calc.GetHash(data), _sha1calc.GetHash(data)];
    }
}

В данном случае, мы запросили в контроллере оба сервиса и вместо типа IHash в полях класса могли бы использовать типы классов, а сами классы могли бы вообще не реализовывать интерфейс — на результат работы это не повлияет.  Теперь мы можем получить два хэша строки, например:

[
  "C446A2994F35689482651B7C7BA8B56C",
  "0CB95B7891FF182F0972BE8754EC934DF65AF21C"
]

Методы расширения для регистрации сервисов

Нередко создаваемые нами сервисы зависят от работы других сервисов. В этом случае бывает не всегда удобно регистрировать по отдельности все необходимые сервисы в контейнере DI. Более того, каждый из сервисов может использовать какие-либо настройки, которые могут дублироваться от сервиса к сервису и так далее. В этом случае, мы можем написать свой метод расширения для IServiceCollection и регистрировать необходимые сервисы, используя всего один метод расширения. Опять же, вернемся к нашему примеру. На данный момент у нас есть два сервиса, зарегистрированные в контейнере DI как обычные классы. В какой-то момент времени мы можем решить, что неплохо было бы иметь один сервис, который бы возвращал нам различные типы хэшей, например, вот такой:

public interface IHashCalculator
{
    public string GetMD5(string data);
    public string GetSHA1(string data);
}

public class HashCalculator : IHashCalculator
{
    private readonly MD5Hash _md5;
    private readonly SHA1Hash _sha1;

    public string GetMD5(string data)
    {
        return _md5.GetHash(data);
    }

    public string GetSHA1(string data)
    {
        return _sha1.GetHash(data);
    }

    public HashCalculator(MD5Hash md5, SHA1Hash sha1)
    { 
        _md5 = md5;
        _sha1 = sha1;
    }
}

В данном случае, наш сервис IHashCalculator зависит от ещё двух сервисов — MD5Hash и SHA1Hash. Следовательно, нам необходимо зарегистрировать в контейнере DI все три сервиса. Для этого можно написать вот такой метод расширения IServiceCollection:

public static class HashExtensions
{
    public static IServiceCollection AddHashCalculator(this IServiceCollection services)
    {
        return services.AddTransient<MD5Hash>()
                       .AddTransient<SHA1Hash>()
                       .AddTransient<IHashCalculator, HashCalculator>();
    }
}

Теперь в Program.cs мы можем зарегистрировать сервисы, используя этот метод:

builder.Services.AddHashCalculator();

запросить сервис в контроллере и использовать его:

[ApiController]
[Route("[controller]")]
public class HashController : Controller
{
    private readonly IHashCalculator _calc;

    public HashController(IHashCalculator calc)
    {
        _calc = calc;
    }

    [HttpGet]
    public string[] Index(string data)
    {
        return [_calc.GetMD5(data), _calc.GetSHA1(data)];
    }
}

Сервисы с ключами (keyed services)

Начиная с .NET 8, регистрируемые сервисы могут связываться с ключами по которым эти сервисы могут запрашиваться. Чтобы разобраться для чего понадобился такой механизм, вернемся к самому первому примеру этой статьи:

public interface IHash
{
    public string GetHash(string data);
}

public class MD5Hash : IHash
{
    public string GetHash(string data)
    {
        return Convert.ToHexString(MD5.HashData(Encoding.UTF8.GetBytes(data)));
    }
}

public class SHA1Hash : IHash 
{
    public string GetHash(string data) 
    {
        return Convert.ToHexString(SHA1.HashData(Encoding.UTF8.GetBytes(data)));
    }
}

Здесь у нас две реализации сервиса. Попробуем ответить на вопрос: какой результат вернет контроллер, если мы произведем вот такую регистрацию сервисов:

builder.Services.AddTransient<IHash, MD5Hash>();
builder.Services.AddTransient<IHash, SHA1Hash>();

и запросим сервис в контроллере:

[ApiController]
[Route("[controller]")]
public class HashController : Controller
{
    private readonly IHash _calc;

    public HashController(IHash calc)
    {
        _calc = calc;
    }

    [HttpGet]
    public string[] Index(string data)
    {
        return [_calc.GetHash(data)];
    }
}

Такой код не вызовет никаких ошибок, приложение запустится и вернет хэш SHA1. Каким образом в этом случае мы можем запросить необходимую нам реализацию сервиса, например, MD5Hash? До .NET 8 для решения такой задачи требовалось использовать, скажем так, не рекомендуемые способы получения сервисов, включая доступ к коллекции сервисов и поиск вручную необходимой реализации.

Использование сервисов с ключами (keyed services) позволяет запросить необходимую реализацию сервиса, используя ключ. Вот как мы можем поступить в .NET 8:

Регистрация сервисов:

builder.Services.AddKeyedTransient<IHash, MD5Hash>("md5");
builder.Services.AddKeyedTransient<IHash, SHA1Hash>("sha1");

Запрос сервиса

public HashController([FromKeyedServices("md5")]IHash calc)
{
    _calc = calc;
}

Для регистрации сервисов мы используем методы AddKeyed...(object? serviceKey), указывая в параметрах ключ сервиса. Чтобы запросить необходимую нам реализацию сервиса мы используем в параметрах конструктора специальный атрибут FromKeyedServices в параметрах которого указываем ключ необходимого нам сервиса. В результате, теперь мы можем без лишних проблем производить множественную регистрацию сервисов и получать необходимые нам реализации, используя ключи.

Итого

Сегодня мы рассмотрели вопросы создания и регистрации сервисов в ASP.NET Core, включая и множественную регистрацию сервисов в приложении. Для регистрации собственных сервисов удобно использовать методы расширения для IServiceCollection, в которых мы можем производить регистрацию сразу всех необходимых нам сервисов. Новые возможности .NET 8 позволяют нам использовать сервисы с ключами (keyed services), благодаря чему мы можем запрашивать необходимую нам реализацию сервиса, используя его ключ.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии