Ответ
Регистрация сервисов происходит в контейнере внедрения зависимостей (DI) на этапе конфигурации приложения. Место зависит от шаблона проекта:
-
В
Program.cs(шаблон .NET 6 и новее): Основное и единственное место в минимальном API.var builder = WebApplication.CreateBuilder(args); // Конфигурация сервисов (DI) builder.Services.AddScoped<IMyService, MyService>(); builder.Services.AddControllers(); var app = builder.Build(); -
В
Startup.ConfigureServices(шаблон .NET Core 3.1, 5): Классический подход.public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddTransient<IEmailSender, EmailSender>(); services.AddRazorPages(); } }
Основные методы регистрации:
| Метод | Время жизни экземпляра | Типичный use-case |
|---|---|---|
AddTransient<TService, TImplementation>() |
Создаётся новый для каждого запроса к контейнеру. | Легковесные, stateless сервисы (например, валидаторы). |
AddScoped<TService, TImplementation>() |
Один экземпляр на протяжении одного HTTP-запроса (scope). | Сервисы, работающие в контексте запроса (например, DbContext в EF Core). |
AddSingleton<TService, TImplementation>() |
Единственный экземпляр на всё время работы приложения. | Сервисы-кэши, конфигурации, логгеры. |
Дополнительные способы:
- Регистрация существующего экземпляра:
services.AddSingleton<IMyService>(existingInstance); - Регистрация через фабричный метод:
services.AddScoped<IMyService>(sp => new MyService("config"));
Ответ 18+ 🔞
А, ну это же про контейнер зависимостей в .NET! Давай разжую, как это работает на практике, а то в документации обычно пишут так, что глаза сломать можно.
Смотри, вся эта магия с сервисами начинается в самом начале, когда приложение только стартует. Ты как бы говоришь системе: «Слушай, вот у меня есть вот такие штуки, вот так их создавай, когда понадобятся». И делаешь ты это в двух основных местах, в зависимости от того, какой у тебя проект.
Первый вариант — современный, в Program.cs. Это когда у тебя шаблон .NET 6 или новее, и всё в одном файле. Выглядит это примерно так:
var builder = WebApplication.CreateBuilder(args);
// Вот тут, блядь, и происходит вся кухня
builder.Services.AddScoped<IMyService, MyService>();
builder.Services.AddControllers();
var app = builder.Build();
Видишь этот builder.Services? Это и есть твоя точка входа. Всё просто и в одном месте.
Второй вариант — классический, через Startup класс. Его ещё в старых шаблонах любят оставлять. Там отдельный метод ConfigureServices:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Кидаешь сюда все свои сервисы, как в корзину
services.AddTransient<IEmailSender, EmailSender>();
services.AddRazorPages();
}
}
Суть одна и та же — ты настраиваешь контейнер до того, как приложение полностью запустится.
А теперь самое важное — как именно регистрировать. Тут есть три кита, и если их перепутать, потом будешь долго искать, почему у тебя данные между пользователями шастают или, наоборот, всё тормозит.
Вот табличка, чтобы не еб*ть себе мозг:
| Метод | Когда живёт экземпляр | Где юзать |
|---|---|---|
AddTransient<IService, RealService>() |
Новый каждый раз, когда кто-то просит. Создали — отдали — забыли. | Всякая легковесная хрень без состояния. Типа валидатора какого-нибудь. Каждый вызов — свежий экземпляр. |
AddScoped<IService, RealService>() |
Один экземпляр на весь HTTP-запрос. Запрос начался — создали. Запрос закончился — выкинули. | Идеально для работы с базой! Тот же DbContext в Entity Framework. Все операции в рамках одного запроса используют один контекст — красота. |
AddSingleton<IService, RealService>() |
Один-единственный экземпляр на всё время работы приложения. Создали при старте и всё. | Сервисы-кэши, логгеры, конфигурация приложения. То, что не должно меняться и может быть общим для всех. |
А, ну и ещё пара фишек, которые могут пригодиться.
Иногда тебе не нужно, чтобы контейнер сам создавал объект. У тебя уже есть готовый экземпляр, прошедший через огонь и воду. Кидай его так:
services.AddSingleton<IMyService>(myPreconfiguredInstance);
А бывает, что объект нужно собрать по-хитрому, с параметрами из конфига или других сервисов. Тогда юзаешь фабрику:
services.AddScoped<IMyService>(serviceProvider =>
{
var config = serviceProvider.GetService<IConfiguration>();
return new MyService(config.GetValue<string>("ApiKey"));
});
Вот, в принципе, и вся основная подноготная. Главное — не выёбывайся и выбирай правильное время жизни, а то потом будут приколы, от которых волосы дыбом встанут. Особенно со Scoped и Singleton не напортачь.