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

