Integration Patterns¶
Overview¶
This section explains how to integrate the Flaggy feature flag library with popular frameworks, libraries, and architectural patterns in .NET applications.
ASP.NET Core Integration¶
Minimal API Integration¶
using Flaggy.Extensions;
using Flaggy.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add Flaggy with MySQL provider
builder.Services.AddFlaggy(options =>
{
options.UseMySQL(
connectionString: builder.Configuration.GetConnectionString("Flaggy"
);
}),
cacheExpiration: TimeSpan.FromMinutes(5)
);
var app = builder.Build();
// Use flags in minimal APIs
app.MapGet("/api/products", async (IFeatureFlagService flagService) =>
{
var useNewApi = await flagService.is_enabledAsync("use-new-product-api");
return useNewApi
? Results.Ok(new { message = "Using new API", version = "v2" })
: Results.Ok(new { message = "Using legacy API", version = "v1" });
});
// Feature-gated endpoint
app.MapGet("/api/beta/analytics", async (IFeatureFlagService flagService) =>
{
if (!await flagService.is_enabledAsync("beta-analytics"))
{
return Results.NotFound(new { error = "Feature not available" });
}
return Results.Ok(new { data = "Analytics data" });
})
.WithName("BetaAnalytics")
.WithTags("Beta");
app.Run();
Controller Integration¶
using Flaggy.Abstractions;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IFeatureFlagService _flagService;
private readonly IProductService _productService;
private readonly ILogger<ProductsController> _logger;
public ProductsController(
IFeatureFlagService flagService,
IProductService productService,
ILogger<ProductsController> logger)
{
_flagService = flagService;
_productService = productService;
_logger = logger;
}
[HttpGet]
public async Task<IActionResult> GetProducts()
{
var useCache = await _flagService.is_enabledAsync("product-caching");
_logger.LogInformation("Getting products (caching: {UseCache})", useCache);
var products = useCache
? await _productService.GetProductsFromCacheAsync()
: await _productService.GetProductsFromDatabaseAsync();
return Ok(products);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
var useNewFormat = await _flagService.is_enabledAsync("new-product-format");
var product = await _productService.GetProductAsync(id);
if (product == null)
return NotFound();
return useNewFormat
? Ok(new ProductDtoV2(product))
: Ok(new ProductDtoV1(product));
}
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody] CreateProductRequest request)
{
var enableValidation = await _flagService.is_enabledAsync("enhanced-product-validation");
if (enableValidation && !await _productService.ValidateEnhancedAsync(request))
{
return BadRequest(new { error = "Enhanced validation failed" });
}
var product = await _productService.CreateProductAsync(request);
return created_atAction(nameof(GetProduct), new { id = product.Id }, product);
}
}
Middleware Integration¶
public class FeatureFlagMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<FeatureFlagMiddleware> _logger;
public FeatureFlagMiddleware(RequestDelegate next, ILogger<FeatureFlagMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, IFeatureFlagService flagService)
{
// Check if maintenance mode is enabled
if (await flagService.is_enabledAsync("maintenance-mode"))
{
context.Response.StatusCode = 503;
context.Response.ContentType = "application/json";
await context.Response.WriteAsJsonAsync(new
{
error = "Service temporarily unavailable for maintenance"
});
return;
}
// Add feature flag context to response headers (for debugging)
if (await flagService.is_enabledAsync("expose-feature-flags"))
{
var enabledFlags = await flagService.GetEnabledFlagsAsync();
context.Response.Headers.Add("X-Feature-Flags",
string.Join(",", enabledFlags.Select(f => f.Key)));
}
await _next(context);
}
}
// Register middleware
app.UseMiddleware<FeatureFlagMiddleware>();
Custom Action Filter¶
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
public class RequireFeatureFlagAttribute : Attribute, IAsyncActionFilter
{
private readonly string _flagKey;
private readonly bool _requiredState;
public RequireFeatureFlagAttribute(string flagKey, bool requiredState = true)
{
_flagKey = flagKey;
_requiredState = requiredState;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var flagService = context.HttpContext.RequestServices
.GetRequiredService<IFeatureFlagService>();
var isEnabled = await flagService.is_enabledAsync(_flagKey);
if (isEnabled != _requiredState)
{
context.Result = new NotFoundObjectResult(new
{
error = "Feature not available",
feature = _flagKey
});
return;
}
await next();
}
}
// Usage in controllers
[ApiController]
[Route("api/[controller]")]
public class BetaController : ControllerBase
{
[HttpGet("new-feature")]
[RequireFeatureFlag("beta-new-feature")]
public IActionResult GetNewFeature()
{
return Ok(new { message = "Beta feature active" });
}
[HttpGet("legacy")]
[RequireFeatureFlag("beta-features", requiredState: false)]
public IActionResult GetLegacyEndpoint()
{
return Ok(new { message = "Legacy endpoint" });
}
}
Dependency Injection Patterns¶
Factory Pattern with Feature Flags¶
public interface IPaymentProcessor
{
Task<PaymentResult> ProcessAsync(Payment payment);
}
public class StripePaymentProcessor : IPaymentProcessor
{
public Task<PaymentResult> ProcessAsync(Payment payment)
{
// Stripe implementation
return Task.FromResult(new PaymentResult { Success = true, Provider = "Stripe" });
}
}
public class PayPalPaymentProcessor : IPaymentProcessor
{
public Task<PaymentResult> ProcessAsync(Payment payment)
{
// PayPal implementation
return Task.FromResult(new PaymentResult { Success = true, Provider = "PayPal" });
}
}
public class PaymentProcessorFactory
{
private readonly IFeatureFlagService _flagService;
private readonly IServiceProvider _serviceProvider;
public PaymentProcessorFactory(
IFeatureFlagService flagService,
IServiceProvider serviceProvider)
{
_flagService = flagService;
_serviceProvider = serviceProvider;
}
public async Task<IPaymentProcessor> CreateAsync()
{
var useStripe = await _flagService.is_enabledAsync("payment-use-stripe");
return useStripe
? _serviceProvider.GetRequiredService<StripePaymentProcessor>()
: _serviceProvider.GetRequiredService<PayPalPaymentProcessor>();
}
}
// Registration
builder.Services.AddScoped<StripePaymentProcessor>();
builder.Services.AddScoped<PayPalPaymentProcessor>();
builder.Services.AddScoped<PaymentProcessorFactory>();
Strategy Pattern with Feature Flags¶
public interface ISearchStrategy
{
Task<SearchResults> SearchAsync(string query);
}
public class BasicSearchStrategy : ISearchStrategy
{
public Task<SearchResults> SearchAsync(string query)
{
// Basic search implementation
return Task.FromResult(new SearchResults());
}
}
public class AdvancedSearchStrategy : ISearchStrategy
{
public Task<SearchResults> SearchAsync(string query)
{
// Advanced ML-powered search
return Task.FromResult(new SearchResults());
}
}
public class SearchService
{
private readonly IFeatureFlagService _flagService;
private readonly BasicSearchStrategy _basicStrategy;
private readonly AdvancedSearchStrategy _advancedStrategy;
public SearchService(
IFeatureFlagService flagService,
BasicSearchStrategy basicStrategy,
AdvancedSearchStrategy advancedStrategy)
{
_flagService = flagService;
_basicStrategy = basicStrategy;
_advancedStrategy = advancedStrategy;
}
public async Task<SearchResults> SearchAsync(string query)
{
var useAdvancedSearch = await _flagService.is_enabledAsync("advanced-search-engine");
var strategy = useAdvancedSearch ? _advancedStrategy : _basicStrategy;
return await strategy.SearchAsync(query);
}
}
MediatR Integration¶
Feature-Flagged Command Handlers¶
using MediatR;
public class CreateOrderCommand : IRequest<OrderResult>
{
public string CustomerId { get; set; }
public List<OrderItem> Items { get; set; }
}
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, OrderResult>
{
private readonly IFeatureFlagService _flagService;
private readonly IOrderRepository _orderRepository;
private readonly IInventoryService _inventoryService;
private readonly ILogger<CreateOrderCommandHandler> _logger;
public CreateOrderCommandHandler(
IFeatureFlagService flagService,
IOrderRepository orderRepository,
IInventoryService inventoryService,
ILogger<CreateOrderCommandHandler> logger)
{
_flagService = flagService;
_orderRepository = orderRepository;
_inventoryService = inventoryService;
_logger = logger;
}
public async Task<OrderResult> Handle(CreateOrderCommand request, CancellationToken ct)
{
// Check if real-time inventory validation is enabled
var validateInventory = await _flagService.is_enabledAsync("realtime-inventory-check");
if (validateInventory)
{
var inventoryValid = await _inventoryService.ValidateAsync(request.Items);
if (!inventoryValid)
{
return OrderResult.Failure("Insufficient inventory");
}
}
var order = await _orderRepository.CreateAsync(request);
_logger.LogInformation(
"Order created: {OrderId} (inventory check: {InventoryCheck})",
order.Id,
validateInventory
);
return OrderResult.Success(order);
}
}
Pipeline Behavior with Feature Flags¶
public class FeatureFlagPipelineBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly IFeatureFlagService _flagService;
private readonly ILogger<FeatureFlagPipelineBehavior<TRequest, TResponse>> _logger;
public FeatureFlagPipelineBehavior(
IFeatureFlagService flagService,
ILogger<FeatureFlagPipelineBehavior<TRequest, TResponse>> logger)
{
_flagService = flagService;
_logger = logger;
}
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var requestType = typeof(TRequest).Name;
// Check if detailed logging is enabled
var detailedLogging = await _flagService.is_enabledAsync("detailed-request-logging");
if (detailedLogging)
{
_logger.LogInformation(
"Handling {RequestType}: {@Request}",
requestType,
request
);
}
var response = await next();
if (detailedLogging)
{
_logger.LogInformation(
"Handled {RequestType}: {@Response}",
requestType,
response
);
}
return response;
}
}
// Registration
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(FeatureFlagPipelineBehavior<,>));
});
Entity Framework Core Integration¶
Feature-Flagged Query Strategies¶
public class ProductRepository : IProductRepository
{
private readonly ApplicationDbContext _context;
private readonly IFeatureFlagService _flagService;
public ProductRepository(ApplicationDbContext context, IFeatureFlagService flagService)
{
_context = context;
_flagService = flagService;
}
public async Task<List<Product>> GetProductsAsync()
{
var useOptimizedQuery = await _flagService.is_enabledAsync("optimized-product-query");
if (useOptimizedQuery)
{
// New optimized query with better indexes
return await _context.Products
.AsNoTracking()
.Where(p => p.IsActive)
.Include(p => p.Category)
.AsSplitQuery()
.OrderBy(p => p.Name)
.ToListAsync();
}
else
{
// Legacy query
return await _context.Products
.Include(p => p.Category)
.Where(p => p.IsActive)
.ToListAsync();
}
}
}
Dynamic Migrations Based on Flags¶
public class ConditionalMigration : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Get flag service from dependency injection
var serviceProvider = new ServiceCollection()
.AddFlaggy(options =>
{
options.UseMySQL(connectionString: connectionString);
})
.BuildServiceProvider();
var flagService = serviceProvider.GetRequiredService<IFeatureFlagService>();
var enableNewFeature = flagService.is_enabledAsync("enable-new-table-structure").Result;
if (enableNewFeature)
{
migrationBuilder.CreateTable(
name: "NewProducts",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(maxLength: 200, nullable: false)
}
);
}
}
}
Background Services Integration¶
Feature-Flagged Background Jobs¶
public class DataSyncBackgroundService : BackgroundService
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger<DataSyncBackgroundService> _logger;
public DataSyncBackgroundService(
IServiceProvider serviceProvider,
ILogger<DataSyncBackgroundService> logger)
{
_serviceProvider = serviceProvider;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using var scope = _serviceProvider.CreateScope();
var flagService = scope.ServiceProvider.GetRequiredService<IFeatureFlagService>();
// Check if sync is enabled
if (await flagService.is_enabledAsync("background-data-sync"))
{
var interval = await flagService.GetValueAsync<int>(
"sync-interval-seconds",
defaultValue: 300
);
_logger.LogInformation("Running data sync (interval: {Interval}s)", interval.Value);
await PerformSyncAsync(scope.ServiceProvider);
await Task.Delay(TimeSpan.FromSeconds(interval.Value), stoppingToken);
}
else
{
_logger.LogInformation("Data sync is disabled, checking again in 60 seconds");
await Task.Delay(TimeSpan.FromSeconds(60), stoppingToken);
}
}
}
private async Task PerformSyncAsync(IServiceProvider serviceProvider)
{
var syncService = serviceProvider.GetRequiredService<IDataSyncService>();
await syncService.SyncAsync();
}
}
// Registration
builder.Services.AddHostedService<DataSyncBackgroundService>();
SignalR Integration¶
Feature-Flagged Real-time Features¶
using Microsoft.AspNetCore.SignalR;
public class NotificationHub : Hub
{
private readonly IFeatureFlagService _flagService;
private readonly ILogger<NotificationHub> _logger;
public NotificationHub(IFeatureFlagService flagService, ILogger<NotificationHub> logger)
{
_flagService = flagService;
_logger = logger;
}
public async Task SendNotification(string message)
{
var useRichNotifications = await _flagService.is_enabledAsync("rich-notifications");
if (useRichNotifications)
{
await Clients.All.SendAsync("ReceiveRichNotification", new
{
Message = message,
Timestamp = DateTime.UtcNow,
Type = "info",
Actions = new[] { "Dismiss", "View Details" }
});
}
else
{
await Clients.All.SendAsync("ReceiveNotification", message);
}
}
public override async Task OnConnectedAsync()
{
var enablePresence = await _flagService.is_enabledAsync("user-presence-tracking");
if (enablePresence)
{
var connectionId = Context.ConnectionId;
_logger.LogInformation("User connected: {ConnectionId}", connectionId);
await Groups.AddToGroupAsync(connectionId, "OnlineUsers");
}
await base.OnConnectedAsync();
}
}
gRPC Integration¶
Feature-Flagged gRPC Services¶
using Grpc.Core;
public class ProductServiceGrpc : ProductService.ProductServiceBase
{
private readonly IFeatureFlagService _flagService;
private readonly IProductRepository _productRepository;
public ProductServiceGrpc(
IFeatureFlagService flagService,
IProductRepository productRepository)
{
_flagService = flagService;
_productRepository = productRepository;
}
public override async Task<GetProductResponse> GetProduct(
GetProductRequest request,
ServerCallContext context)
{
var useCache = await _flagService.is_enabledAsync("grpc-product-caching");
var product = useCache
? await _productRepository.GetFromCacheAsync(request.Id)
: await _productRepository.GetAsync(request.Id);
if (product == null)
{
throw new RpcException(new Status(StatusCode.NotFound, "Product not found"));
}
var includeExtendedInfo = await _flagService.is_enabledAsync("grpc-extended-product-info");
return new GetProductResponse
{
Id = product.Id,
Name = product.Name,
Price = product.Price,
ExtendedInfo = includeExtendedInfo ? product.ExtendedInfo : null
};
}
}
Health Checks Integration¶
Feature Flag Health Check¶
using Microsoft.Extensions.Diagnostics.HealthChecks;
public class FeatureFlagHealthCheck : IHealthCheck
{
private readonly IFeatureFlagService _flagService;
private readonly ILogger<FeatureFlagHealthCheck> _logger;
public FeatureFlagHealthCheck(
IFeatureFlagService flagService,
ILogger<FeatureFlagHealthCheck> logger)
{
_flagService = flagService;
_logger = logger;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
var sw = Stopwatch.StartNew();
// Try to get a flag to verify service is working
await _flagService.GetAllFlagsAsync(cancellationToken);
sw.Stop();
var data = new Dictionary<string, object>
{
{ "response_time_ms", sw.ElapsedMilliseconds }
};
// Check critical flags
var maintenanceMode = await _flagService.is_enabledAsync("maintenance-mode", cancellationToken);
data["maintenance_mode"] = maintenanceMode;
if (maintenanceMode)
{
return HealthCheckResult.Degraded(
"Feature flag service is in maintenance mode",
data: data
);
}
if (sw.ElapsedMilliseconds > 1000)
{
return HealthCheckResult.Degraded(
$"Feature flag service is slow: {sw.ElapsedMilliseconds}ms",
data: data
);
}
return HealthCheckResult.Healthy(
"Feature flag service is healthy",
data: data
);
}
catch (Exception ex)
{
_logger.LogError(ex, "Feature flag health check failed");
return HealthCheckResult.Unhealthy(
"Feature flag service is unavailable",
ex
);
}
}
}
// Registration
builder.Services.AddHealthChecks()
.AddCheck<FeatureFlagHealthCheck>("feature_flags", tags: new[] { "ready" });
app.MapHealthChecks("/health", new HealthCheckOptions
{
ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse
});
Serilog Integration¶
Enriching Logs with Feature Flag Context¶
using Serilog;
using Serilog.Core;
using Serilog.Events;
public class FeatureFlagEnricher : ILogEventEnricher
{
private readonly IFeatureFlagService _flagService;
public FeatureFlagEnricher(IFeatureFlagService flagService)
{
_flagService = flagService;
}
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
{
// Add important feature flags to log context
var experimentalMode = _flagService.is_enabledAsync("experimental-mode").Result;
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty(
"ExperimentalMode", experimentalMode));
}
}
// Configuration
builder.Host.UseSerilog((context, services, configuration) =>
{
configuration
.ReadFrom.Configuration(context.Configuration)
.Enrich.With(services.GetRequiredService<FeatureFlagEnricher>());
});
FluentValidation Integration¶
Conditional Validation Rules¶
using FluentValidation;
public class CreateProductRequestValidator : AbstractValidator<CreateProductRequest>
{
private readonly IFeatureFlagService _flagService;
public CreateProductRequestValidator(IFeatureFlagService flagService)
{
_flagService = flagService;
RuleFor(x => x.Name)
.NotEmpty()
.MaximumLength(200);
RuleFor(x => x.Price)
.GreaterThan(0);
// Conditional validation based on feature flag
When(x => _flagService.is_enabledAsync("strict-product-validation").Result, () =>
{
RuleFor(x => x.description)
.NotEmpty()
.MinimumLength(50)
.WithMessage("description must be at least 50 characters when strict validation is enabled");
RuleFor(x => x.CategoryId)
.NotEmpty()
.WithMessage("Category is required when strict validation is enabled");
});
}
}
AutoMapper Integration¶
Feature-Flagged Mapping Profiles¶
using AutoMapper;
public class ProductMappingProfile : Profile
{
public ProductMappingProfile()
{
CreateMap<Product, ProductDto>();
CreateMap<Product, ProductDtoV2>()
.ForMember(dest => dest.ExtendedProperties, opt => opt.Ignore());
}
}
public class ProductService
{
private readonly IMapper _mapper;
private readonly IFeatureFlagService _flagService;
public ProductService(IMapper mapper, IFeatureFlagService flagService)
{
_mapper = mapper;
_flagService = flagService;
}
public async Task<object> GetProductDtoAsync(Product product)
{
var useV2Format = await _flagService.is_enabledAsync("product-dto-v2");
return useV2Format
? _mapper.Map<ProductDtoV2>(product)
: _mapper.Map<ProductDto>(product);
}
}
Polly Integration (Resilience Patterns)¶
Feature-Flagged Retry Policies¶
using Polly;
using Polly.Extensions.Http;
public static class ResiliencePolicies
{
public static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy(IFeatureFlagService flagService)
{
var useAggressiveRetry = flagService.is_enabledAsync("aggressive-http-retry").Result;
var retryCount = useAggressiveRetry ? 5 : 3;
var retryDelay = useAggressiveRetry ? TimeSpan.FromSeconds(1) : TimeSpan.FromSeconds(2);
return HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
retryCount,
retryAttempt => retryDelay * retryAttempt
);
}
}
// Registration
builder.Services.AddHttpClient("ProductApi", client =>
{
client.BaseAddress = new Uri("https://api.example.com");
})
.AddPolicyHandler(sp =>
{
var flagService = sp.GetRequiredService<IFeatureFlagService>();
return ResiliencePolicies.GetRetryPolicy(flagService);
});
Hangfire Integration¶
Feature-Flagged Background Jobs¶
using Hangfire;
public class RecurringJobsConfiguration
{
private readonly IFeatureFlagService _flagService;
public RecurringJobsConfiguration(IFeatureFlagService flagService)
{
_flagService = flagService;
}
public async Task ConfigureJobsAsync()
{
// Enable/disable jobs based on feature flags
if (await _flagService.is_enabledAsync("daily-report-job"))
{
RecurringJob.AddOrUpdate<ReportService>(
"daily-reports",
service => service.GenerateDailyReportAsync(),
Cron.Daily
);
}
else
{
RecurringJob.RemoveIfExists("daily-reports");
}
if (await _flagService.is_enabledAsync("data-cleanup-job"))
{
var interval = await _flagService.GetValueAsync("cleanup-cron", defaultValue: Cron.Weekly());
RecurringJob.AddOrUpdate<CleanupService>(
"data-cleanup",
service => service.CleanupOldDataAsync(),
interval
);
}
}
}
Testing Integration Patterns¶
Integration Test with Feature Flags¶
public class ProductApiIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
private readonly WebApplicationFactory<Program> _factory;
public ProductApiIntegrationTests(WebApplicationFactory<Program> factory)
{
_factory = factory;
}
[Fact]
public async Task GetProducts_WithCachingEnabled_ReturnsCachedData()
{
// Arrange
var client = _factory.WithWebHostBuilder(builder =>
{
builder.ConfigureServices(services =>
{
// Replace with test flag service
var descriptor = services.SingleOrDefault(
d => d.ServiceType == typeof(IFeatureFlagService));
services.Remove(descriptor);
services.AddScoped<IFeatureFlagService>(sp =>
{
var testServices = new ServiceCollection();
testServices.AddFlaggy(new InMemoryFeatureFlagProvider());
var provider = testServices.BuildServiceProvider();
var flagService = provider.GetRequiredService<IFeatureFlagService>();
// Enable caching for this test
flagService.CreateFlagAsync(new FeatureFlag
{
Key = "product-caching",
is_enabled = true
}).Wait();
return flagService;
});
});
}).CreateClient();
// Act
var response = await client.GetAsync("/api/products");
// Assert
response.EnsureSuccessStatusCode();
var responseHeader = response.Headers.GetValues("X-Cache-Status").FirstOrDefault();
Assert.Equal("HIT", responseHeader);
}
}