Где можно зарегистрировать сервис в ASP.NET Core?

Ответ

Регистрация сервисов происходит в контейнере внедрения зависимостей (DI) на этапе конфигурации приложения. Место зависит от шаблона проекта:

  1. В Program.cs (шаблон .NET 6 и новее): Основное и единственное место в минимальном API.

    var builder = WebApplication.CreateBuilder(args);
    // Конфигурация сервисов (DI)
    builder.Services.AddScoped<IMyService, MyService>();
    builder.Services.AddControllers();
    var app = builder.Build();
  2. В 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 не напортачь.