From c1b7d312e86b6dfc28f036fc306829952852a6a3 Mon Sep 17 00:00:00 2001 From: fffffatah Date: Sun, 10 Mar 2024 03:17:33 +0600 Subject: [PATCH 1/2] Add getting features from database example --- Microsoft.FeatureManagement.sln | 7 +++ .../Constants/FeatureConstants.cs | 6 ++ .../Controllers/WeatherForecastController.cs | 35 +++++++++++ .../CustomFeatureDefinitionProvider.cs | 60 +++++++++++++++++++ .../Database/Feature.cs | 8 +++ .../Database/ServiceCollectionExtensions.cs | 20 +++++++ .../Database/Services/FeatureService.cs | 27 +++++++++ .../Database/Services/IFeatureService.cs | 8 +++ .../Database/SqliteDbContext.cs | 17 ++++++ .../GettingFeaturesFromDatabase.csproj | 16 +++++ .../GettingFeaturesFromDatabase/Program.cs | 23 +++++++ .../WeatherForecast.cs | 12 ++++ .../appsettings.Development.json | 8 +++ .../appsettings.json | 9 +++ 14 files changed, 256 insertions(+) create mode 100644 examples/GettingFeaturesFromDatabase/Constants/FeatureConstants.cs create mode 100644 examples/GettingFeaturesFromDatabase/Controllers/WeatherForecastController.cs create mode 100644 examples/GettingFeaturesFromDatabase/CustomFeatureDefinitionProvider.cs create mode 100644 examples/GettingFeaturesFromDatabase/Database/Feature.cs create mode 100644 examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs create mode 100644 examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs create mode 100644 examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs create mode 100644 examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs create mode 100644 examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj create mode 100644 examples/GettingFeaturesFromDatabase/Program.cs create mode 100644 examples/GettingFeaturesFromDatabase/WeatherForecast.cs create mode 100644 examples/GettingFeaturesFromDatabase/appsettings.Development.json create mode 100644 examples/GettingFeaturesFromDatabase/appsettings.json diff --git a/Microsoft.FeatureManagement.sln b/Microsoft.FeatureManagement.sln index c7fcfcc2..b7b5dc97 100644 --- a/Microsoft.FeatureManagement.sln +++ b/Microsoft.FeatureManagement.sln @@ -25,6 +25,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TargetingConsoleApp", "exam EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorServerApp", "examples\BlazorServerApp\BlazorServerApp.csproj", "{12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GettingFeaturesFromDatabase", "examples\GettingFeaturesFromDatabase\GettingFeaturesFromDatabase.csproj", "{C58C3CF1-756A-4A3B-9591-99F0D372B208}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -67,6 +69,10 @@ Global {12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Debug|Any CPU.Build.0 = Debug|Any CPU {12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Release|Any CPU.ActiveCfg = Release|Any CPU {12BAB5A6-4EEB-4917-B5D9-4AFB6253008E}.Release|Any CPU.Build.0 = Release|Any CPU + {C58C3CF1-756A-4A3B-9591-99F0D372B208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C58C3CF1-756A-4A3B-9591-99F0D372B208}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C58C3CF1-756A-4A3B-9591-99F0D372B208}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C58C3CF1-756A-4A3B-9591-99F0D372B208}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -79,6 +85,7 @@ Global {DACAB624-4611-42E8-844C-529F93A54980} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {283D3EBB-4716-4F1D-BA51-A435F7E2AB82} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} {12BAB5A6-4EEB-4917-B5D9-4AFB6253008E} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} + {C58C3CF1-756A-4A3B-9591-99F0D372B208} = {FB5C34DF-695C-4DF9-8AED-B3EA2516EA72} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {84DA6C54-F140-4518-A1B4-E4CF42117FBD} diff --git a/examples/GettingFeaturesFromDatabase/Constants/FeatureConstants.cs b/examples/GettingFeaturesFromDatabase/Constants/FeatureConstants.cs new file mode 100644 index 00000000..ed7774ce --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Constants/FeatureConstants.cs @@ -0,0 +1,6 @@ +namespace GettingFeaturesFromDatabase.Constants; + +public static class FeatureConstants +{ + public const string Weather = "Weather"; +} diff --git a/examples/GettingFeaturesFromDatabase/Controllers/WeatherForecastController.cs b/examples/GettingFeaturesFromDatabase/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..fa752ae7 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Controllers/WeatherForecastController.cs @@ -0,0 +1,35 @@ +using GettingFeaturesFromDatabase.Constants; +using Microsoft.AspNetCore.Mvc; +using Microsoft.FeatureManagement.Mvc; + +namespace GettingFeaturesFromDatabase.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + [FeatureGate(FeatureConstants.Weather)] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} diff --git a/examples/GettingFeaturesFromDatabase/CustomFeatureDefinitionProvider.cs b/examples/GettingFeaturesFromDatabase/CustomFeatureDefinitionProvider.cs new file mode 100644 index 00000000..6a48cee1 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/CustomFeatureDefinitionProvider.cs @@ -0,0 +1,60 @@ +using GettingFeaturesFromDatabase.Database; +using GettingFeaturesFromDatabase.Database.Services; +using Microsoft.FeatureManagement; + +namespace GettingFeaturesFromDatabase; + +public class CustomFeatureDefinitionProvider : IFeatureDefinitionProvider +{ + private readonly IFeatureService _featureService; + + public CustomFeatureDefinitionProvider(IFeatureService featureService) + { + _featureService = featureService; + } + + public async Task GetFeatureDefinitionAsync(string featureName) + { + var feature = await _featureService.GetFeatureAsync(featureName); + + return GenerateFeatureDefinition(feature); + } + + public async IAsyncEnumerable GetAllFeatureDefinitionsAsync() + { + var features = await _featureService.GetFeatureAsync(); + + foreach (var feature in features) + { + yield return GenerateFeatureDefinition(feature); + } + } + + private FeatureDefinition GenerateFeatureDefinition(Feature? feature) + { + if (feature is null) + { + return new FeatureDefinition(); + } + + if (feature.IsEnabled) + { + return new FeatureDefinition + { + Name = feature.Name, + EnabledFor = new[] + { + new FeatureFilterConfiguration + { + Name = "AlwaysOn", + }, + }, + }; + } + + return new FeatureDefinition + { + Name = feature.Name, + }; + } +} diff --git a/examples/GettingFeaturesFromDatabase/Database/Feature.cs b/examples/GettingFeaturesFromDatabase/Database/Feature.cs new file mode 100644 index 00000000..bb7ae350 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Database/Feature.cs @@ -0,0 +1,8 @@ +namespace GettingFeaturesFromDatabase.Database; + +public class Feature +{ + public string Name { get; set; } + + public bool IsEnabled { get; set; } +} diff --git a/examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs b/examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..73aafc9c --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs @@ -0,0 +1,20 @@ +using GettingFeaturesFromDatabase.Database.Services; +using Microsoft.EntityFrameworkCore; + +namespace GettingFeaturesFromDatabase.Database; + +public static class ServiceCollectionExtensions +{ + public static void AddDatabase(this IServiceCollection services) + { + services.AddDbContext(options => + { + options.UseSqlite("Data Source=example.db"); + }); + } + + public static void AddFeatureService(this IServiceCollection services) + { + services.AddScoped(); + } +} diff --git a/examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs b/examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs new file mode 100644 index 00000000..6902f3e0 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; + +namespace GettingFeaturesFromDatabase.Database.Services; + +public class FeatureService : IFeatureService +{ + private readonly SqliteDbContext _sqliteDbContext; + + public FeatureService(SqliteDbContext sqliteDbContext) + { + _sqliteDbContext = sqliteDbContext; + } + + public async Task GetFeatureAsync(string featureName) + { + var feature = await _sqliteDbContext.Set().FindAsync(featureName); + + return feature; + } + + public async Task> GetFeatureAsync() + { + var features = await _sqliteDbContext.Set().ToListAsync(); + + return features; + } +} diff --git a/examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs b/examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs new file mode 100644 index 00000000..09fc62cb --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs @@ -0,0 +1,8 @@ +namespace GettingFeaturesFromDatabase.Database.Services; + +public interface IFeatureService +{ + Task GetFeatureAsync(string featureName); + + Task> GetFeatureAsync(); +} diff --git a/examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs b/examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs new file mode 100644 index 00000000..b6bcdc0e --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; + +namespace GettingFeaturesFromDatabase.Database; + +public class SqliteDbContext : DbContext +{ + public DbSet Features { get; set; } = null!; + + public SqliteDbContext(DbContextOptions options) : base(options) + { + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity().HasKey(x => x.Name); + } +} diff --git a/examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj b/examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj new file mode 100644 index 00000000..8582ed70 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/examples/GettingFeaturesFromDatabase/Program.cs b/examples/GettingFeaturesFromDatabase/Program.cs new file mode 100644 index 00000000..3da60a01 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Program.cs @@ -0,0 +1,23 @@ +using GettingFeaturesFromDatabase; +using GettingFeaturesFromDatabase.Database; +using Microsoft.FeatureManagement; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddDatabase(); +builder.Services.AddFeatureService(); + +/* Add feature management */ +builder.Services.AddScoped().AddScopedFeatureManagement(); + +var app = builder.Build(); + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/examples/GettingFeaturesFromDatabase/WeatherForecast.cs b/examples/GettingFeaturesFromDatabase/WeatherForecast.cs new file mode 100644 index 00000000..afdfc728 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace GettingFeaturesFromDatabase; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/examples/GettingFeaturesFromDatabase/appsettings.Development.json b/examples/GettingFeaturesFromDatabase/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/examples/GettingFeaturesFromDatabase/appsettings.json b/examples/GettingFeaturesFromDatabase/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From a262e1e5fa695e85d9beaae9a4a78980a85df4c4 Mon Sep 17 00:00:00 2001 From: fffffatah Date: Sun, 10 Mar 2024 20:49:24 +0600 Subject: [PATCH 2/2] Simplify example for initial run --- .../Controllers/FeatureController.cs | 29 ++++++++++++ .../Database/FeatureConfiguration.cs | 22 +++++++++ .../Database/ServiceCollectionExtensions.cs | 5 ++ .../Database/Services/FeatureService.cs | 10 +++- .../Database/Services/IFeatureService.cs | 4 +- .../Database/SqliteDbContext.cs | 6 ++- .../GettingFeaturesFromDatabase.csproj | 4 +- .../GettingFeaturesFromDatabase/Program.cs | 7 +++ .../GettingFeaturesFromDatabase/README.md | 47 +++++++++++++++++++ 9 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 examples/GettingFeaturesFromDatabase/Controllers/FeatureController.cs create mode 100644 examples/GettingFeaturesFromDatabase/Database/FeatureConfiguration.cs create mode 100644 examples/GettingFeaturesFromDatabase/README.md diff --git a/examples/GettingFeaturesFromDatabase/Controllers/FeatureController.cs b/examples/GettingFeaturesFromDatabase/Controllers/FeatureController.cs new file mode 100644 index 00000000..43874956 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Controllers/FeatureController.cs @@ -0,0 +1,29 @@ +using GettingFeaturesFromDatabase.Database; +using GettingFeaturesFromDatabase.Database.Services; +using Microsoft.AspNetCore.Mvc; + +namespace GettingFeaturesFromDatabase.Controllers; + +[ApiController] +[Route("feature")] +public class FeatureController : ControllerBase +{ + private readonly IFeatureService _featureService; + + public FeatureController(IFeatureService featureService) + { + _featureService = featureService; + } + + [HttpGet] + public async Task> GetFeatures() + { + return await _featureService.GetFeatureAsync(); + } + + [HttpPut] + public async Task UpdateFeature(string featureName, bool isEnabled) + { + await _featureService.UpdateFeatureAsync(featureName, isEnabled); + } +} diff --git a/examples/GettingFeaturesFromDatabase/Database/FeatureConfiguration.cs b/examples/GettingFeaturesFromDatabase/Database/FeatureConfiguration.cs new file mode 100644 index 00000000..7068ba6c --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/Database/FeatureConfiguration.cs @@ -0,0 +1,22 @@ +using GettingFeaturesFromDatabase.Constants; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace GettingFeaturesFromDatabase.Database; + +internal sealed class FeatureConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.Property(x => x.Name).IsRequired(); + builder.Property(x => x.IsEnabled).IsRequired(); + + /* Populate with feature data. */ + builder.HasData(new List() + { + new Feature { Name = FeatureConstants.Weather, IsEnabled = true }, + }); + + builder.ToTable("Features").HasKey(x => x.Name); + } +} diff --git a/examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs b/examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs index 73aafc9c..0608da95 100644 --- a/examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs +++ b/examples/GettingFeaturesFromDatabase/Database/ServiceCollectionExtensions.cs @@ -11,6 +11,11 @@ public static void AddDatabase(this IServiceCollection services) { options.UseSqlite("Data Source=example.db"); }); + + /* Create Database On Start */ + var databaseContext = services.BuildServiceProvider().GetRequiredService(); + databaseContext.Database.EnsureDeleted(); + databaseContext.Database.EnsureCreated(); } public static void AddFeatureService(this IServiceCollection services) diff --git a/examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs b/examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs index 6902f3e0..dd37ff05 100644 --- a/examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs +++ b/examples/GettingFeaturesFromDatabase/Database/Services/FeatureService.cs @@ -18,10 +18,18 @@ public FeatureService(SqliteDbContext sqliteDbContext) return feature; } - public async Task> GetFeatureAsync() + public async Task> GetFeatureAsync() { var features = await _sqliteDbContext.Set().ToListAsync(); return features; } + + public async Task UpdateFeatureAsync(string featureName, bool isEnabled) + { + var feature = await _sqliteDbContext.Set().FindAsync(featureName); + if (feature != null) feature.IsEnabled = isEnabled; + + await _sqliteDbContext.SaveChangesAsync(); + } } diff --git a/examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs b/examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs index 09fc62cb..3d264a9d 100644 --- a/examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs +++ b/examples/GettingFeaturesFromDatabase/Database/Services/IFeatureService.cs @@ -4,5 +4,7 @@ public interface IFeatureService { Task GetFeatureAsync(string featureName); - Task> GetFeatureAsync(); + Task> GetFeatureAsync(); + + Task UpdateFeatureAsync(string featureName, bool isEnabled); } diff --git a/examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs b/examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs index b6bcdc0e..24a45fb9 100644 --- a/examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs +++ b/examples/GettingFeaturesFromDatabase/Database/SqliteDbContext.cs @@ -9,9 +9,11 @@ public class SqliteDbContext : DbContext public SqliteDbContext(DbContextOptions options) : base(options) { } - + protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity().HasKey(x => x.Name); + base.OnModelCreating(modelBuilder); + + modelBuilder.ApplyConfiguration(new FeatureConfiguration()); } } diff --git a/examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj b/examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj index 8582ed70..c1afa6c6 100644 --- a/examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj +++ b/examples/GettingFeaturesFromDatabase/GettingFeaturesFromDatabase.csproj @@ -8,7 +8,9 @@ - + + + diff --git a/examples/GettingFeaturesFromDatabase/Program.cs b/examples/GettingFeaturesFromDatabase/Program.cs index 3da60a01..649a22c8 100644 --- a/examples/GettingFeaturesFromDatabase/Program.cs +++ b/examples/GettingFeaturesFromDatabase/Program.cs @@ -6,6 +6,7 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); builder.Services.AddDatabase(); builder.Services.AddFeatureService(); @@ -14,6 +15,12 @@ var app = builder.Build(); +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + app.UseHttpsRedirection(); app.UseAuthorization(); diff --git a/examples/GettingFeaturesFromDatabase/README.md b/examples/GettingFeaturesFromDatabase/README.md new file mode 100644 index 00000000..865c2cd7 --- /dev/null +++ b/examples/GettingFeaturesFromDatabase/README.md @@ -0,0 +1,47 @@ +# Getting features from database (Web API Example) + +This example demonstrates how to get features from a database and use them with Microsoft Feature Management. + +## Quickstart + +To get started, + +1. Simply run the project `GettingFeaturesFromDatabase` and you should see **Swagger UI** open in a new browser window. +2. Try out the `GET /WeatherForecast` endpoint and see the response. It should return a successful response (200 OK). +3. Now, try out `PUT /feature` endpoint to set the state of 'Weather' feature flag to 'false'. +4. Try out the `GET /WeatherForecast` endpoint again and see the response. It should return not found response (404 NotFound). + +_Note: You can get the list of features using `GET /feature` endpoint._ + +## About the example project + +This example is a simple Web API project with Entity Framework Core and SQLite database. + +The endpoints are, + +1. `GET /WeatherForecast` - This is the endpoint where `FeatureGate` attribute is used and access to this endpoint is controlled by the state of 'Weather' feature flag. +2. `GET /feature` - This is the endpoint to get all the feature flags from the database. +3. `PUT /feature` - This is the endpoint to update the state of a feature flag in the database. The query parameters are 'featureName' and 'isEnabled'. + +## Seeding the database + +During the initial run of the project, an SQLite database file named `example.db` will be created in the root of `GettingFeaturesFromDatabase` directory. +Initial run would also create a table named 'Features' and populate it with a feature flag named 'Weather'. + +You can find the database seeding logic inside `/Database/FeatureConfiguration.cs` file. + +```csharp + public void Configure(EntityTypeBuilder builder) + { + builder.Property(x => x.Name).IsRequired(); + builder.Property(x => x.IsEnabled).IsRequired(); + + /* Populate with feature data. */ + builder.HasData(new List() + { + new Feature { Name = FeatureConstants.Weather, IsEnabled = true }, + }); + + builder.ToTable("Features").HasKey(x => x.Name); + } +```