diff --git a/Dotnet.Samples.AspNetCore.WebApi/Program.cs b/Dotnet.Samples.AspNetCore.WebApi/Program.cs index d8c798d..e96ebf4 100644 --- a/Dotnet.Samples.AspNetCore.WebApi/Program.cs +++ b/Dotnet.Samples.AspNetCore.WebApi/Program.cs @@ -2,6 +2,7 @@ using Dotnet.Samples.AspNetCore.WebApi.Data; using Dotnet.Samples.AspNetCore.WebApi.Mappings; using Dotnet.Samples.AspNetCore.WebApi.Services; +using Dotnet.Samples.AspNetCore.WebApi.Utilities; using Microsoft.EntityFrameworkCore; using Microsoft.OpenApi.Models; using Serilog; @@ -20,26 +21,22 @@ * Logging * -------------------------------------------------------------------------- */ Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Configuration).CreateLogger(); - builder.Host.UseSerilog(); /* ----------------------------------------------------------------------------- * Services * -------------------------------------------------------------------------- */ - builder.Services.AddControllers(); - -var dataSource = - $"{AppDomain.CurrentDomain.SetupInformation.ApplicationBase}/Data/players-sqlite3.db"; - builder.Services.AddDbContextPool(options => { + var dataSource = Path.Combine(AppContext.BaseDirectory, "Data", "players-sqlite3.db"); options.UseSqlite($"Data Source={dataSource}"); - if (builder.Environment.IsDevelopment()) { options.EnableSensitiveDataLogging(); - options.LogTo(Console.WriteLine, LogLevel.Information); + options.LogTo(Log.Logger.Information, LogLevel.Information); + // https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-9.0/whatsnew#improved-data-seeding + options.UseAsyncSeeding(DbContextUtils.SeedAsync); } }); @@ -84,9 +81,6 @@ * Middlewares * https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware * -------------------------------------------------------------------------- */ - -app.UseSerilogRequestLogging(); - if (app.Environment.IsDevelopment()) { // https://learn.microsoft.com/en-us/aspnet/core/tutorials/getting-started-with-swashbuckle @@ -94,6 +88,9 @@ app.UseSwaggerUI(); } +// https://github.com/serilog/serilog-aspnetcore +app.UseSerilogRequestLogging(); + // https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl app.UseHttpsRedirection(); @@ -103,11 +100,4 @@ // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/routing#endpoints app.MapControllers(); -/* ----------------------------------------------------------------------------- - * Data Seeding - * https://learn.microsoft.com/en-us/ef/core/modeling/data-seeding - * -------------------------------------------------------------------------- */ - -app.SeedDbContext(); - await app.RunAsync(); diff --git a/Dotnet.Samples.AspNetCore.WebApi/Utilities/ApplicationBuilderExtensions.cs b/Dotnet.Samples.AspNetCore.WebApi/Utilities/ApplicationBuilderExtensions.cs index d98be6c..e3fcab0 100644 --- a/Dotnet.Samples.AspNetCore.WebApi/Utilities/ApplicationBuilderExtensions.cs +++ b/Dotnet.Samples.AspNetCore.WebApi/Utilities/ApplicationBuilderExtensions.cs @@ -1,24 +1,38 @@ -namespace Dotnet.Samples.AspNetCore.WebApi.Data; +using Dotnet.Samples.AspNetCore.WebApi.Data; +using Microsoft.EntityFrameworkCore; + +namespace Dotnet.Samples.AspNetCore.WebApi.Utilities; public static class ApplicationBuilderExtensions { /// - /// Simple extension method to populate the database with an initial set of data. + /// Async extension method to populate the database with initial data /// - public static void SeedDbContext(this IApplicationBuilder app) + public static async Task SeedDbContextAsync(this IApplicationBuilder app) { using var scope = app.ApplicationServices.CreateScope(); - var dbContext = scope.ServiceProvider.GetService(); + var services = scope.ServiceProvider; + var logger = services.GetRequiredService>(); + var dbContext = services.GetRequiredService(); - if (dbContext != null) + try { - // https://learn.microsoft.com/en-us/ef/core/managing-schemas/ensure-created - dbContext.Database.EnsureCreated(); + await dbContext.Database.EnsureCreatedAsync(); - if (!dbContext.Players.Any()) + if (!await dbContext.Players.AnyAsync()) { - dbContext.Players.AddRange(PlayerData.CreateStarting11()); + await dbContext.Players.AddRangeAsync(PlayerData.CreateStarting11()); + await dbContext.SaveChangesAsync(); + logger.LogInformation("Successfully seeded database with initial data."); } } + catch (Exception exception) + { + logger.LogError(exception, "An error occurred while seeding the database"); + throw new InvalidOperationException( + "An error occurred while seeding the database", + exception + ); + } } } diff --git a/Dotnet.Samples.AspNetCore.WebApi/Utilities/DbContextUtils.cs b/Dotnet.Samples.AspNetCore.WebApi/Utilities/DbContextUtils.cs new file mode 100644 index 0000000..b43b5af --- /dev/null +++ b/Dotnet.Samples.AspNetCore.WebApi/Utilities/DbContextUtils.cs @@ -0,0 +1,42 @@ +using Dotnet.Samples.AspNetCore.WebApi.Data; +using Microsoft.EntityFrameworkCore; + +namespace Dotnet.Samples.AspNetCore.WebApi.Utilities; + +public static class DbContextUtils +{ + /// + /// Seeds the database with initial data if empty. + /// + /// + /// This method checks if the database is empty and seeds it with initial data. + /// It is designed to be used with UseAsyncSeeding. + /// + /// The database context to seed. + /// Unused parameter, required by the UseAsyncSeeding API. + /// A token to cancel the operation if needed. + /// A task representing the asynchronous seeding operation. + public static Task SeedAsync(DbContext context, bool _, CancellationToken cancellationToken) + { + if (context is not PlayerDbContext playerDbContext) + { + throw new ArgumentException( + $"Expected context of type {nameof(PlayerDbContext)}, but got {context.GetType().Name}", + nameof(context) + ); + } + return SeedPlayersAsync(playerDbContext, cancellationToken); + } + + private static async Task SeedPlayersAsync( + PlayerDbContext dbContext, + CancellationToken cancellationToken + ) + { + if (!await dbContext.Players.AnyAsync(cancellationToken)) + { + await dbContext.Players.AddRangeAsync(PlayerData.CreateStarting11(), cancellationToken); + await dbContext.SaveChangesAsync(cancellationToken); + } + } +} diff --git a/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerDataExtensions.cs b/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerDataExtensions.cs deleted file mode 100644 index 3a32bc9..0000000 --- a/Dotnet.Samples.AspNetCore.WebApi/Utilities/PlayerDataExtensions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Dotnet.Samples.AspNetCore.WebApi.Models; - -namespace Dotnet.Samples.AspNetCore.WebApi.Data; - -public static class PlayerDataExtensions -{ - /// - /// Simple extension method to map all properties of a Player - /// - public static void MapFrom(this Player target, Player source) - { - target.FirstName = source.FirstName; - target.MiddleName = source.MiddleName; - target.LastName = source.LastName; - target.DateOfBirth = source.DateOfBirth; - target.SquadNumber = source.SquadNumber; - target.Position = source.Position; - target.AbbrPosition = source.AbbrPosition; - target.Team = source.Team; - target.League = source.League; - target.Starting11 = source.Starting11; - } -}