Содержание
Чаще всего, в качестве типа сервиса выступает какой-либо интерфейс, однако, это обстоятельство не является обязательным — в качестве сервиса может выступать и обычных класс. Рассмотрим возможные варианты создания сервисов и их регистрации в контейнере 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), благодаря чему мы можем запрашивать необходимую нам реализацию сервиса, используя его ключ.