Фича-флаги в C#: как включать фичи без передеплоя

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое фича-флаги и зачем они вам нужны

Представьте: вы написали крутую новую фичу — например, умный поиск с автодополнением. Всё протестировано, код прошёл ревью. Но вы не уверены на 100%, что пользователи её примут. Или боитесь, что под нагрузкой она ляжет. Что делать?

Раньше ответ был прост: «Залей в прод и молись». Сегодня мы умнее. Используем фича-флаги (feature flags, feature toggles) — механизм, который позволяет включать и выключать функциональность на лету, без перекомпиляции и повторного деплоя.

В этой статье на PythonLib мы разберём, как внедрить фича-флаги в C# проектах — от простейшей реализации до продвинутых сценариев с A/B тестированием и канареечными релизами.

Почему фича-флаги — это мастхэв для C# разработчика

Feature flags C# решают три ключевые задачи:

  • Безопасный деплой: заливайте код в прод, даже если фича ещё не готова. Просто выключите флаг.
  • Мгновенное отключение: что-то пошло не так? Один клик в админке — и проблемная фича исчезла.
  • Гибкое управление функциями: включайте фичи для конкретных пользователей, регионов или по времени.

И самое приятное: для управления функциями в .NET не нужно изобретать велосипед — есть готовая библиотека Microsoft.FeatureManagement.

Самый простой способ: конфигурационный файл

Начнём с базового подхода — через appsettings.json. Это идеально для простых feature toggles .NET.

Шаг 1. Добавляем настройки

{
  "FeatureManagement": {
    "NewSearchEngine": true,
    "DarkMode": false,
    "BetaDashboard": true
  }
}

Шаг 2. Регистрируем сервис

В Program.cs добавляем:

builder.Services.AddFeatureManagement();

Шаг 3. Используем в коде

public class SearchController
{
    private readonly IFeatureManager _featureManager;

    public SearchController(IFeatureManager featureManager)
    {
        _featureManager = featureManager;
    }

    public async Task<IActionResult> Search(string query)
    {
        if (await _featureManager.IsEnabledAsync("NewSearchEngine"))
        {
            return View("NewSearch", await _newEngine.SearchAsync(query));
        }
        return View("LegacySearch", await _oldEngine.SearchAsync(query));
    }
}

Готово! Теперь, чтобы включить новую фичу, достаточно поменять true на false в конфиге и перезапустить приложение. Но мы же хотим без перезапуска?

Динамические фича-флаги: переключение без рестарта

Для conditional features C# без перезагрузки используем провайдеры конфигурации, которые подгружают изменения. Лучший вариант — база данных или Redis.

Создаём провайдер из базы данных

public class DatabaseFeatureDefinitionProvider : IFeatureDefinitionProvider
{
    private readonly AppDbContext _db;

    public DatabaseFeatureDefinitionProvider(AppDbContext db)
    {
        _db = db;
    }

    public async Task<FeatureDefinition> GetFeatureDefinitionAsync(string featureName)
    {
        var feature = await _db.FeatureFlags
            .FirstOrDefaultAsync(f => f.Name == featureName);
        
        return feature != null 
            ? new FeatureDefinition { Name = feature.Name, EnabledFor = feature.EnabledFor }
            : null;
    }

    public IAsyncEnumerable<FeatureDefinition> GetAllFeatureDefinitionsAsync()
    {
        return _db.FeatureFlags
            .Select(f => new FeatureDefinition { Name = f.Name, EnabledFor = f.EnabledFor })
            .AsAsyncEnumerable();
    }
}

Регистрируем:

builder.Services.AddFeatureManagement()
    .AddFeatureDefinitionProvider<DatabaseFeatureDefinitionProvider>();

Теперь изменения в БД применяются мгновенно — идеально для soft launch C#.

Продвинутые сценарии: фильтры и A/B тестирование

Библиотека Microsoft.FeatureManagement поддерживает мощные фильтры. Это открывает дорогу для A/B тестирования C# и канареечных релизов.

Процентное включение

{
  "FeatureManagement": {
    "NewCheckout": {
      "EnabledFor": [
        {
          "Name": "Microsoft.Percentage",
          "Parameters": {
            "Value": 25
          }
        }
      ]
    }
  }
}

25% пользователей увидят новый интерфейс оформления заказа. Остальные — старый.

Включение по времени

{
  "FeatureManagement": {
    "HolidayTheme": {
      "EnabledFor": [
        {
          "Name": "Microsoft.TimeWindow",
          "Parameters": {
            "Start": "2024-12-01T00:00:00Z",
            "End": "2025-01-10T23:59:59Z"
          }
        }
      ]
    }
  }
}

Новогодняя тема включится автоматически 1 декабря и отключится 10 января. Никаких ручных действий.

Кастомный фильтр для конкретного пользователя

Создаём свой фильтр для canary release C#:

[FilterAlias("UserGroup")]
public class UserGroupFilter : IFeatureFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public UserGroupFilter(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
    {
        var user = _httpContextAccessor.HttpContext.User;
        var settings = context.Parameters.Get<UserGroupFilterSettings>();
        
        return Task.FromResult(
            user.IsInRole(settings.GroupName) || 
            user.HasClaim("beta-tester", "true")
        );
    }
}

public class UserGroupFilterSettings
{
    public string GroupName { get; set; }
}

Использование:

{
  "FeatureManagement": {
    "PremiumFeature": {
      "EnabledFor": [
        {
          "Name": "UserGroup",
          "Parameters": {
            "GroupName": "PremiumUsers"
          }
        }
      ]
    }
  }
}

Когда фича-флаги — это плохая идея

Честно: не всё так радужно. Тогглы функций .NET имеют тёмную сторону. Вот когда их лучше не использовать: