diff --git a/.editorconfig b/.editorconfig index 9261202f8..88b30b088 100644 --- a/.editorconfig +++ b/.editorconfig @@ -58,13 +58,23 @@ dotnet_style_prefer_conditional_expression_over_return = true:silent ############################### # Style Definitions dotnet_naming_style.pascal_case_style.capitalization = pascal_case -# Use PascalCase for constant fields +# Use PascalCase for constant fields dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.applicable_accessibilities = * dotnet_naming_symbols.constant_fields.required_modifiers = const + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ ############################### # C# Coding Conventions # ############################### diff --git a/eShopOnWeb.sln b/eShopOnWeb.sln index b99fc4ec4..0394e30ba 100755 --- a/eShopOnWeb.sln +++ b/eShopOnWeb.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29102.190 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{419A6ACE-0419-4315-A6FB-B0E63D39432E}" EndProject @@ -23,6 +23,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "tests\Fu EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0BD72BEA-EF42-4B72-8B69-12A39EC76FBA}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig docker-compose.override.yml = docker-compose.override.yml docker-compose.yml = docker-compose.yml .github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml diff --git a/src/BlazorAdmin/Pages/CatalogItemPage/List.razor b/src/BlazorAdmin/Pages/CatalogItemPage/List.razor index d8749d773..33d34c585 100644 --- a/src/BlazorAdmin/Pages/CatalogItemPage/List.razor +++ b/src/BlazorAdmin/Pages/CatalogItemPage/List.razor @@ -3,6 +3,8 @@ @inherits BlazorAdmin.Helpers.BlazorComponent @namespace BlazorAdmin.Pages.CatalogItemPage +eShopOnWeb Admin: Manage Product Catalog +

Manage Product Catalog

@if (catalogItems == null) diff --git a/src/BlazorAdmin/Program.cs b/src/BlazorAdmin/Program.cs index b80094751..a6d4354d4 100644 --- a/src/BlazorAdmin/Program.cs +++ b/src/BlazorAdmin/Program.cs @@ -1,54 +1,49 @@ using System; using System.Net.Http; using System.Threading.Tasks; +using BlazorAdmin; using BlazorAdmin.Services; using Blazored.LocalStorage; using BlazorShared; using BlazorShared.Models; using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace BlazorAdmin; +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#admin"); +builder.RootComponents.Add("head::after"); -public class Program -{ - public static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("#admin"); - - var configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); - builder.Services.Configure(configSection); +var configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); +builder.Services.Configure(configSection); - builder.Services.AddScoped(sp => new HttpClient() { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddScoped(sp => new HttpClient() { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - builder.Services.AddScoped(); - builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); - builder.Services.AddBlazoredLocalStorage(); +builder.Services.AddBlazoredLocalStorage(); - builder.Services.AddAuthorizationCore(); - builder.Services.AddScoped(); - builder.Services.AddScoped(sp => (CustomAuthStateProvider)sp.GetRequiredService()); +builder.Services.AddAuthorizationCore(); +builder.Services.AddScoped(); +builder.Services.AddScoped(sp => (CustomAuthStateProvider)sp.GetRequiredService()); - builder.Services.AddBlazorServices(); +builder.Services.AddBlazorServices(); - builder.Logging.AddConfiguration(builder.Configuration.GetRequiredSection("Logging")); +builder.Logging.AddConfiguration(builder.Configuration.GetRequiredSection("Logging")); - await ClearLocalStorageCache(builder.Services); +await ClearLocalStorageCache(builder.Services); - await builder.Build().RunAsync(); - } +await builder.Build().RunAsync(); - private static async Task ClearLocalStorageCache(IServiceCollection services) - { - var sp = services.BuildServiceProvider(); - var localStorageService = sp.GetRequiredService(); +static async Task ClearLocalStorageCache(IServiceCollection services) +{ + var sp = services.BuildServiceProvider(); + var localStorageService = sp.GetRequiredService(); - await localStorageService.RemoveItemAsync(typeof(CatalogBrand).Name); - await localStorageService.RemoveItemAsync(typeof(CatalogType).Name); - } + await localStorageService.RemoveItemAsync(typeof(CatalogBrand).Name); + await localStorageService.RemoveItemAsync(typeof(CatalogType).Name); } diff --git a/src/Infrastructure/Data/CatalogContextSeed.cs b/src/Infrastructure/Data/CatalogContextSeed.cs index 7fbd88193..e1364f52e 100644 --- a/src/Infrastructure/Data/CatalogContextSeed.cs +++ b/src/Infrastructure/Data/CatalogContextSeed.cs @@ -10,7 +10,8 @@ namespace Microsoft.eShopWeb.Infrastructure.Data; public class CatalogContextSeed { public static async Task SeedAsync(CatalogContext catalogContext, - ILoggerFactory loggerFactory, int retry = 0) + ILogger logger, + int retry = 0) { var retryForAvailability = retry; try @@ -49,9 +50,9 @@ await catalogContext.CatalogItems.AddRangeAsync( if (retryForAvailability >= 10) throw; retryForAvailability++; - var log = loggerFactory.CreateLogger(); - log.LogError(ex.Message); - await SeedAsync(catalogContext, loggerFactory, retryForAvailability); + + logger.LogError(ex.Message); + await SeedAsync(catalogContext, logger, retryForAvailability); throw; } } diff --git a/src/PublicApi/AuthEndpoints/Authenticate.cs b/src/PublicApi/AuthEndpoints/Authenticate.cs index 61d576dd7..eee5218d2 100644 --- a/src/PublicApi/AuthEndpoints/Authenticate.cs +++ b/src/PublicApi/AuthEndpoints/Authenticate.cs @@ -9,9 +9,9 @@ namespace Microsoft.eShopWeb.PublicApi.AuthEndpoints; -public class Authenticate : BaseAsyncEndpoint +public class Authenticate : EndpointBaseAsync .WithRequest - .WithResponse + .WithActionResult { private readonly SignInManager _signInManager; private readonly ITokenClaimsService _tokenClaimsService; diff --git a/src/PublicApi/CatalogBrandEndpoints/List.cs b/src/PublicApi/CatalogBrandEndpoints/List.cs index 1524f4db2..d64b8ca13 100644 --- a/src/PublicApi/CatalogBrandEndpoints/List.cs +++ b/src/PublicApi/CatalogBrandEndpoints/List.cs @@ -10,9 +10,9 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogBrandEndpoints; -public class List : BaseAsyncEndpoint +public class List : EndpointBaseAsync .WithoutRequest - .WithResponse + .WithActionResult { private readonly IRepository _catalogBrandRepository; private readonly IMapper _mapper; diff --git a/src/PublicApi/CatalogItemEndpoints/Create.cs b/src/PublicApi/CatalogItemEndpoints/Create.cs index 74ee7ed1a..da9b885ab 100644 --- a/src/PublicApi/CatalogItemEndpoints/Create.cs +++ b/src/PublicApi/CatalogItemEndpoints/Create.cs @@ -13,9 +13,9 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public class Create : BaseAsyncEndpoint +public class Create : EndpointBaseAsync .WithRequest - .WithResponse + .WithActionResult { private readonly IRepository _itemRepository; private readonly IUriComposer _uriComposer; diff --git a/src/PublicApi/CatalogItemEndpoints/Delete.cs b/src/PublicApi/CatalogItemEndpoints/Delete.cs index 9b1173204..1724bc41c 100644 --- a/src/PublicApi/CatalogItemEndpoints/Delete.cs +++ b/src/PublicApi/CatalogItemEndpoints/Delete.cs @@ -11,9 +11,9 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public class Delete : BaseAsyncEndpoint +public class Delete : EndpointBaseAsync .WithRequest - .WithResponse + .WithActionResult { private readonly IRepository _itemRepository; diff --git a/src/PublicApi/CatalogItemEndpoints/GetById.cs b/src/PublicApi/CatalogItemEndpoints/GetById.cs index 722f221f6..62352cfef 100644 --- a/src/PublicApi/CatalogItemEndpoints/GetById.cs +++ b/src/PublicApi/CatalogItemEndpoints/GetById.cs @@ -8,9 +8,9 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; -public class GetById : BaseAsyncEndpoint +public class GetById : EndpointBaseAsync .WithRequest - .WithResponse + .WithActionResult { private readonly IRepository _itemRepository; private readonly IUriComposer _uriComposer; diff --git a/src/PublicApi/CatalogItemEndpoints/ListPaged.cs b/src/PublicApi/CatalogItemEndpoints/ListPaged.cs index 9feffa7d8..c7c20f761 100644 --- a/src/PublicApi/CatalogItemEndpoints/ListPaged.cs +++ b/src/PublicApi/CatalogItemEndpoints/ListPaged.cs @@ -12,9 +12,9 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; -public class ListPaged : BaseAsyncEndpoint +public class ListPaged : EndpointBaseAsync .WithRequest - .WithResponse + .WithActionResult { private readonly IRepository _itemRepository; private readonly IUriComposer _uriComposer; diff --git a/src/PublicApi/CatalogItemEndpoints/Update.cs b/src/PublicApi/CatalogItemEndpoints/Update.cs index 3d674b216..a960162ab 100644 --- a/src/PublicApi/CatalogItemEndpoints/Update.cs +++ b/src/PublicApi/CatalogItemEndpoints/Update.cs @@ -12,9 +12,9 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogItemEndpoints; [Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] -public class Update : BaseAsyncEndpoint +public class Update : EndpointBaseAsync .WithRequest - .WithResponse + .WithActionResult { private readonly IRepository _itemRepository; private readonly IUriComposer _uriComposer; diff --git a/src/PublicApi/CatalogTypeEndpoints/List.cs b/src/PublicApi/CatalogTypeEndpoints/List.cs index 7e40aac88..eb1b21231 100644 --- a/src/PublicApi/CatalogTypeEndpoints/List.cs +++ b/src/PublicApi/CatalogTypeEndpoints/List.cs @@ -10,9 +10,9 @@ namespace Microsoft.eShopWeb.PublicApi.CatalogTypeEndpoints; -public class List : BaseAsyncEndpoint +public class List : EndpointBaseAsync .WithoutRequest - .WithResponse + .WithActionResult { private readonly IRepository _catalogTypeRepository; private readonly IMapper _mapper; diff --git a/src/PublicApi/MiddleWares/ExceptionMiddleware.cs b/src/PublicApi/Middleware/ExceptionMiddleware.cs similarity index 95% rename from src/PublicApi/MiddleWares/ExceptionMiddleware.cs rename to src/PublicApi/Middleware/ExceptionMiddleware.cs index bae4800a0..46b24f8c8 100644 --- a/src/PublicApi/MiddleWares/ExceptionMiddleware.cs +++ b/src/PublicApi/Middleware/ExceptionMiddleware.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.eShopWeb.ApplicationCore.Exceptions; -namespace Microsoft.eShopWeb.PublicApi.MiddleWares; +namespace Microsoft.eShopWeb.PublicApi.Middleware; public class ExceptionMiddleware { diff --git a/src/PublicApi/Program.cs b/src/PublicApi/Program.cs index a1c2b2448..176d9becd 100644 --- a/src/PublicApi/Program.cs +++ b/src/PublicApi/Program.cs @@ -1,50 +1,186 @@ -using System; +using System; +using System.Collections.Generic; +using System.Text; using System.Threading.Tasks; +using BlazorShared; +using BlazorShared.Models; +using MediatR; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb; +using Microsoft.eShopWeb.ApplicationCore.Constants; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; +using Microsoft.eShopWeb.ApplicationCore.Services; using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; +using Microsoft.eShopWeb.Infrastructure.Logging; +using Microsoft.eShopWeb.PublicApi; +using Microsoft.eShopWeb.PublicApi.Middleware; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; -namespace Microsoft.eShopWeb.PublicApi; +var builder = WebApplication.CreateBuilder(args); -public class Program +builder.Logging.AddConsole(); + +// use real database +// Requires LocalDB which can be installed with SQL Server Express 2016 +// https://www.microsoft.com/en-us/download/details.aspx?id=54284 +builder.Services.AddDbContext(c => + c.UseSqlServer(builder.Configuration.GetConnectionString("CatalogConnection"))); + +// Add Identity DbContext +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityConnection"))); + +builder.Services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + +builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); +builder.Services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>)); +builder.Services.Configure(builder.Configuration); +builder.Services.AddSingleton(new UriComposer(builder.Configuration.Get())); +builder.Services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); +builder.Services.AddScoped(); + +var configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); +builder.Services.Configure(configSection); +var baseUrlConfig = configSection.Get(); + +builder.Services.AddMemoryCache(); + +var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); +builder.Services.AddAuthentication(config => +{ + config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; +}) +.AddJwtBearer(config => { - public static async Task Main(string[] args) + config.RequireHttpsMetadata = false; + config.SaveToken = true; + config.TokenValidationParameters = new TokenValidationParameters { - var host = CreateHostBuilder(args) - .Build(); - - using (var scope = host.Services.CreateScope()) - { - var services = scope.ServiceProvider; - var loggerFactory = services.GetRequiredService(); - try - { - var catalogContext = services.GetRequiredService(); - await CatalogContextSeed.SeedAsync(catalogContext, loggerFactory); - - var userManager = services.GetRequiredService>(); - var roleManager = services.GetRequiredService>(); - await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager); - } - catch (Exception ex) - { - var logger = loggerFactory.CreateLogger(); - logger.LogError(ex, "An error occurred seeding the DB."); - } - } + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = false, + ValidateAudience = false + }; +}); - host.Run(); - } +const string CORS_POLICY = "CorsPolicy"; +builder.Services.AddCors(options => +{ + options.AddPolicy(name: CORS_POLICY, + builder => + { + builder.WithOrigins(baseUrlConfig.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/')); + builder.AllowAnyMethod(); + builder.AllowAnyHeader(); + }); +}); + +builder.Services.AddControllers(); + +builder.Services.AddMediatR(typeof(CatalogItem).Assembly); +builder.Services.AddAutoMapper(typeof(MappingProfile).Assembly); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); + c.EnableAnnotations(); + c.SchemaFilter(); + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n + Enter 'Bearer' [space] and then your token in the text input below. + \r\n\r\nExample: 'Bearer 12345abcdef'", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" + }); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => + c.AddSecurityRequirement(new OpenApiSecurityRequirement() { - webBuilder.UseStartup(); + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + }, + Scheme = "oauth2", + Name = "Bearer", + In = ParameterLocation.Header, + + }, + new List() + } }); +}); + +var app = builder.Build(); + +app.Logger.LogInformation("PublicApi App created..."); + +if (app.Environment.IsDevelopment()) +{ + app.UseDeveloperExceptionPage(); } + +app.UseMiddleware(); + +app.UseHttpsRedirection(); + +app.UseRouting(); + +app.UseCors(CORS_POLICY); + +app.UseAuthorization(); + +// Enable middleware to serve generated Swagger as a JSON endpoint. +app.UseSwagger(); + +// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), +// specifying the Swagger JSON endpoint. +app.UseSwaggerUI(c => +{ + c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); +}); + +app.UseEndpoints(endpoints => +{ + endpoints.MapControllers(); +}); + +app.Logger.LogInformation("Seeding Database..."); + +using (var scope = app.Services.CreateScope()) +{ + var scopedProvider = scope.ServiceProvider; + try + { + var catalogContext = scopedProvider.GetRequiredService(); + await CatalogContextSeed.SeedAsync(catalogContext, app.Logger); + + var userManager = scopedProvider.GetRequiredService>(); + var roleManager = scopedProvider.GetRequiredService>(); + await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager); + } + catch (Exception ex) + { + app.Logger.LogError(ex, "An error occurred seeding the DB."); + } +} + +app.Logger.LogInformation("LAUNCHING PublicApi"); +app.Run(); diff --git a/src/PublicApi/PublicApi.csproj b/src/PublicApi/PublicApi.csproj index 76b3b9ced..72ad7a8b1 100644 --- a/src/PublicApi/PublicApi.csproj +++ b/src/PublicApi/PublicApi.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/PublicApi/Startup.cs b/src/PublicApi/Startup.cs deleted file mode 100644 index 2a52fd586..000000000 --- a/src/PublicApi/Startup.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using AutoMapper; -using BlazorShared; -using MediatR; -using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Constants; -using Microsoft.eShopWeb.ApplicationCore.Entities; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.eShopWeb.ApplicationCore.Services; -using Microsoft.eShopWeb.Infrastructure.Data; -using Microsoft.eShopWeb.Infrastructure.Identity; -using Microsoft.eShopWeb.Infrastructure.Logging; -using Microsoft.eShopWeb.Infrastructure.Services; -using Microsoft.eShopWeb.PublicApi.MiddleWares; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.IdentityModel.Tokens; -using Microsoft.OpenApi.Models; - -namespace Microsoft.eShopWeb.PublicApi; - -public class Startup -{ - private const string CORS_POLICY = "CorsPolicy"; - - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureDevelopmentServices(IServiceCollection services) - { - // use in-memory database - ConfigureInMemoryDatabases(services); - - // use real database - //ConfigureProductionServices(services); - } - - public void ConfigureDockerServices(IServiceCollection services) - { - ConfigureDevelopmentServices(services); - } - - private void ConfigureInMemoryDatabases(IServiceCollection services) - { - services.AddDbContext(c => - c.UseInMemoryDatabase("Catalog")); - - services.AddDbContext(options => - options.UseInMemoryDatabase("Identity")); - - ConfigureServices(services); - } - - public void ConfigureProductionServices(IServiceCollection services) - { - // use real database - // Requires LocalDB which can be installed with SQL Server Express 2016 - // https://www.microsoft.com/en-us/download/details.aspx?id=54284 - services.AddDbContext(c => - c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"))); - - // Add Identity DbContext - services.AddDbContext(options => - options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection"))); - - ConfigureServices(services); - } - - public void ConfigureTestingServices(IServiceCollection services) - { - ConfigureInMemoryDatabases(services); - } - - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>)); - services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>)); - services.Configure(Configuration); - services.AddSingleton(new UriComposer(Configuration.Get())); - services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>)); - services.AddScoped(); - - var configSection = Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); - services.Configure(configSection); - var baseUrlConfig = configSection.Get(); - - services.AddMemoryCache(); - - var key = Encoding.ASCII.GetBytes(AuthorizationConstants.JWT_SECRET_KEY); - services.AddAuthentication(config => - { - config.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; - }) - .AddJwtBearer(config => - { - config.RequireHttpsMetadata = false; - config.SaveToken = true; - config.TokenValidationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(key), - ValidateIssuer = false, - ValidateAudience = false - }; - }); - - services.AddCors(options => - { - options.AddPolicy(name: CORS_POLICY, - builder => - { - builder.WithOrigins(baseUrlConfig.WebBase.Replace("host.docker.internal", "localhost").TrimEnd('/')); - builder.AllowAnyMethod(); - builder.AllowAnyHeader(); - }); - }); - - services.AddControllers(); - services.AddMediatR(typeof(CatalogItem).Assembly); - - services.AddAutoMapper(typeof(Startup).Assembly); - services.AddSwaggerGen(c => - { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); - c.EnableAnnotations(); - c.SchemaFilter(); - c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - { - Description = @"JWT Authorization header using the Bearer scheme. \r\n\r\n - Enter 'Bearer' [space] and then your token in the text input below. - \r\n\r\nExample: 'Bearer 12345abcdef'", - Name = "Authorization", - In = ParameterLocation.Header, - Type = SecuritySchemeType.ApiKey, - Scheme = "Bearer" - }); - - c.AddSecurityRequirement(new OpenApiSecurityRequirement() - { - { - new OpenApiSecurityScheme - { - Reference = new OpenApiReference - { - Type = ReferenceType.SecurityScheme, - Id = "Bearer" - }, - Scheme = "oauth2", - Name = "Bearer", - In = ParameterLocation.Header, - - }, - new List() - } - }); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseMiddleware(); - - app.UseHttpsRedirection(); - - app.UseRouting(); - - app.UseCors(CORS_POLICY); - - app.UseAuthorization(); - - // Enable middleware to serve generated Swagger as a JSON endpoint. - app.UseSwagger(); - - // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), - // specifying the Swagger JSON endpoint. - app.UseSwaggerUI(c => - { - c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1"); - }); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllers(); - }); - } -} diff --git a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs index 4d04c069e..eed08342e 100644 --- a/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/src/Web/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -53,7 +53,7 @@ public class InputModel public bool RememberMe { get; set; } } - public async Task OnGetAsync(string returnUrl = null) + public async Task OnGetAsync(string? returnUrl = null) { if (!string.IsNullOrEmpty(ErrorMessage)) { @@ -70,7 +70,7 @@ public async Task OnGetAsync(string returnUrl = null) ReturnUrl = returnUrl; } - public async Task OnPostAsync(string returnUrl = null) + public async Task OnPostAsync(string? returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); diff --git a/src/Web/Interfaces/IBasketViewModelService.cs b/src/Web/Interfaces/IBasketViewModelService.cs index 85d1c8773..d7042deaa 100644 --- a/src/Web/Interfaces/IBasketViewModelService.cs +++ b/src/Web/Interfaces/IBasketViewModelService.cs @@ -1,5 +1,4 @@ -using System.Threading.Tasks; -using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; +using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate; using Microsoft.eShopWeb.Web.Pages.Basket; namespace Microsoft.eShopWeb.Web.Interfaces; diff --git a/src/Web/Program.cs b/src/Web/Program.cs index 6455a29b6..c3944a97d 100644 --- a/src/Web/Program.cs +++ b/src/Web/Program.cs @@ -1,49 +1,198 @@ -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Hosting; +using System.Net.Mime; +using Ardalis.ListStartupServices; +using BlazorAdmin; +using BlazorAdmin.Services; +using Blazored.LocalStorage; +using BlazorShared; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.EntityFrameworkCore; +using Microsoft.eShopWeb; +using Microsoft.eShopWeb.ApplicationCore.Interfaces; using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; +using Microsoft.eShopWeb.Web; +using Microsoft.eShopWeb.Web.Configuration; +using Microsoft.eShopWeb.Web.HealthChecks; +using Microsoft.Extensions.Diagnostics.HealthChecks; -namespace Microsoft.eShopWeb.Web; +var builder = WebApplication.CreateBuilder(args); -public class Program +builder.Logging.AddConsole(); + +// use real database +// Requires LocalDB which can be installed with SQL Server Express 2016 +// https://www.microsoft.com/en-us/download/details.aspx?id=54284 +builder.Services.AddDbContext(c => + c.UseSqlServer(builder.Configuration.GetConnectionString("CatalogConnection"))); + +// Add Identity DbContext +builder.Services.AddDbContext(options => + options.UseSqlServer(builder.Configuration.GetConnectionString("IdentityConnection"))); + +builder.Services.AddCookieSettings(); + +builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.Cookie.HttpOnly = true; + options.Cookie.SecurePolicy = CookieSecurePolicy.Always; + options.Cookie.SameSite = SameSiteMode.Lax; + }); + +builder.Services.AddIdentity() + .AddDefaultUI() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + +builder.Services.AddScoped(); + +builder.Services.AddCoreServices(builder.Configuration); +builder.Services.AddWebServices(builder.Configuration); + +// Add memory cache services +builder.Services.AddMemoryCache(); +builder.Services.AddRouting(options => +{ + // Replace the type and the name used to refer to it with your own + // IOutboundParameterTransformer implementation + options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); +}); + +builder.Services.AddMvc(options => +{ + options.Conventions.Add(new RouteTokenTransformerConvention( + new SlugifyParameterTransformer())); + +}); +builder.Services.AddControllersWithViews(); +builder.Services.AddRazorPages(options => +{ + options.Conventions.AuthorizePage("/Basket/Checkout"); +}); +builder.Services.AddHttpContextAccessor(); +builder.Services + .AddHealthChecks() + .AddCheck("api_health_check", tags: new[] { "apiHealthCheck" }) + .AddCheck("home_page_health_check", tags: new[] { "homePageHealthCheck" }); +builder.Services.Configure(config => +{ + config.Services = new List(builder.Services); + config.Path = "/allservices"; +}); + +// blazor configuration +var configSection = builder.Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); +builder.Services.Configure(configSection); +var baseUrlConfig = configSection.Get(); + +// Blazor Admin Required Services for Prerendering +builder.Services.AddScoped(s => new HttpClient +{ + BaseAddress = new Uri(baseUrlConfig.WebBase) +}); + +// add blazor services +builder.Services.AddBlazoredLocalStorage(); +builder.Services.AddServerSideBlazor(); + + +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddBlazorServices(); + +builder.Services.AddDatabaseDeveloperPageExceptionFilter(); + +//_services = services; // used to debug registered services + +var app = builder.Build(); + +app.Logger.LogInformation("App created..."); + +var catalogBaseUrl = builder.Configuration.GetValue(typeof(string), "CatalogBaseUrl") as string; +if (!string.IsNullOrEmpty(catalogBaseUrl)) { - public static async Task Main(string[] args) + app.Use((context, next) => { - var host = CreateHostBuilder(args) - .Build(); + context.Request.PathBase = new PathString(catalogBaseUrl); + return next(); + }); +} - using (var scope = host.Services.CreateScope()) +app.UseHealthChecks("/health", + new HealthCheckOptions + { + ResponseWriter = async (context, report) => { - var services = scope.ServiceProvider; - var loggerFactory = services.GetRequiredService(); - try - { - var catalogContext = services.GetRequiredService(); - await CatalogContextSeed.SeedAsync(catalogContext, loggerFactory); - - var userManager = services.GetRequiredService>(); - var roleManager = services.GetRequiredService>(); - await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager); - } - catch (Exception ex) + var result = new { - var logger = loggerFactory.CreateLogger(); - logger.LogError(ex, "An error occurred seeding the DB."); - } + status = report.Status.ToString(), + errors = report.Entries.Select(e => new + { + key = e.Key, + value = Enum.GetName(typeof(HealthStatus), e.Value.Status) + }) + }.ToJson(); + context.Response.ContentType = MediaTypeNames.Application.Json; + await context.Response.WriteAsync(result); } + }); +if (app.Environment.IsDevelopment()) +{ + app.Logger.LogInformation("Adding Development middleware..."); + app.UseDeveloperExceptionPage(); + app.UseShowAllServicesMiddleware(); + app.UseMigrationsEndPoint(); + app.UseWebAssemblyDebugging(); +} +else +{ + app.Logger.LogInformation("Adding non-Development middleware..."); + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); +} - host.Run(); - } +app.UseHttpsRedirection(); +app.UseBlazorFrameworkFiles(); +app.UseStaticFiles(); +app.UseRouting(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); +app.UseCookiePolicy(); +app.UseAuthentication(); +app.UseAuthorization(); + +app.UseEndpoints(endpoints => +{ + endpoints.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}"); + endpoints.MapRazorPages(); + endpoints.MapHealthChecks("home_page_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("homePageHealthCheck") }); + endpoints.MapHealthChecks("api_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("apiHealthCheck") }); + //endpoints.MapBlazorHub("/admin"); + endpoints.MapFallbackToFile("index.html"); +}); + +app.Logger.LogInformation("Seeding Database..."); + +using (var scope = app.Services.CreateScope()) +{ + var scopedProvider = scope.ServiceProvider; + try + { + var catalogContext = scopedProvider.GetRequiredService(); + await CatalogContextSeed.SeedAsync(catalogContext, app.Logger); + + var userManager = scopedProvider.GetRequiredService>(); + var roleManager = scopedProvider.GetRequiredService>(); + await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager); + } + catch (Exception ex) + { + app.Logger.LogError(ex, "An error occurred seeding the DB."); + } } + +app.Logger.LogInformation("LAUNCHING"); +app.Run(); diff --git a/src/Web/Startup.cs b/src/Web/Startup.cs deleted file mode 100644 index d854a6ffd..000000000 --- a/src/Web/Startup.cs +++ /dev/null @@ -1,241 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Mime; -using Ardalis.ListStartupServices; -using BlazorAdmin; -using BlazorAdmin.Services; -using Blazored.LocalStorage; -using BlazorShared; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.Diagnostics.HealthChecks; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.EntityFrameworkCore; -using Microsoft.eShopWeb.ApplicationCore.Interfaces; -using Microsoft.eShopWeb.Infrastructure.Data; -using Microsoft.eShopWeb.Infrastructure.Identity; -using Microsoft.eShopWeb.Web.Configuration; -using Microsoft.eShopWeb.Web.HealthChecks; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Diagnostics.HealthChecks; -using Microsoft.Extensions.Hosting; - -namespace Microsoft.eShopWeb.Web; - -public class Startup -{ - private IServiceCollection _services; - - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - public void ConfigureDevelopmentServices(IServiceCollection services) - { - // use in-memory database - ConfigureInMemoryDatabases(services); - - // use real database - //ConfigureProductionServices(services); - } - - public void ConfigureDockerServices(IServiceCollection services) - { - services.AddDataProtection() - .SetApplicationName("eshopwebmvc") - .PersistKeysToFileSystem(new DirectoryInfo(@"./")); - - ConfigureDevelopmentServices(services); - } - - private void ConfigureInMemoryDatabases(IServiceCollection services) - { - // use in-memory database - services.AddDbContext(c => - c.UseInMemoryDatabase("Catalog")); - - // Add Identity DbContext - services.AddDbContext(options => - options.UseInMemoryDatabase("Identity")); - - ConfigureServices(services); - } - - public void ConfigureProductionServices(IServiceCollection services) - { - // use real database - // Requires LocalDB which can be installed with SQL Server Express 2016 - // https://www.microsoft.com/en-us/download/details.aspx?id=54284 - services.AddDbContext(c => - c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection"))); - - // Add Identity DbContext - services.AddDbContext(options => - options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection"))); - - ConfigureServices(services); - } - - public void ConfigureTestingServices(IServiceCollection services) - { - ConfigureInMemoryDatabases(services); - } - - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddCookieSettings(); - - - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(options => - { - options.Cookie.HttpOnly = true; - options.Cookie.SecurePolicy = CookieSecurePolicy.Always; - options.Cookie.SameSite = SameSiteMode.Lax; - }); - - services.AddIdentity() - .AddDefaultUI() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - services.AddScoped(); - - services.AddCoreServices(Configuration); - services.AddWebServices(Configuration); - - // Add memory cache services - services.AddMemoryCache(); - services.AddRouting(options => - { - // Replace the type and the name used to refer to it with your own - // IOutboundParameterTransformer implementation - options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); - }); - services.AddMvc(options => - { - options.Conventions.Add(new RouteTokenTransformerConvention( - new SlugifyParameterTransformer())); - - }); - services.AddControllersWithViews(); - services.AddRazorPages(options => - { - options.Conventions.AuthorizePage("/Basket/Checkout"); - }); - services.AddHttpContextAccessor(); - services - .AddHealthChecks() - .AddCheck("api_health_check", tags: new[] { "apiHealthCheck" }) - .AddCheck("home_page_health_check", tags: new[] { "homePageHealthCheck" }); - services.Configure(config => - { - config.Services = new List(services); - - config.Path = "/allservices"; - }); - - var configSection = Configuration.GetRequiredSection(BaseUrlConfiguration.CONFIG_NAME); - services.Configure(configSection); - var baseUrlConfig = configSection.Get(); - - // Blazor Admin Required Services for Prerendering - services.AddScoped(s => new HttpClient - { - BaseAddress = new Uri(baseUrlConfig.WebBase) - }); - - // add blazor services - services.AddBlazoredLocalStorage(); - services.AddServerSideBlazor(); - - - services.AddScoped(); - services.AddScoped(); - services.AddBlazorServices(); - - services.AddDatabaseDeveloperPageExceptionFilter(); - - _services = services; // used to debug registered services - } - - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - var catalogBaseUrl = Configuration.GetValue(typeof(string), "CatalogBaseUrl") as string; - if (!string.IsNullOrEmpty(catalogBaseUrl)) - { - app.Use((context, next) => - { - context.Request.PathBase = new PathString(catalogBaseUrl); - return next(); - }); - } - - app.UseHealthChecks("/health", - new HealthCheckOptions - { - ResponseWriter = async (context, report) => - { - var result = new - { - status = report.Status.ToString(), - errors = report.Entries.Select(e => new - { - key = e.Key, - value = Enum.GetName(typeof(HealthStatus), e.Value.Status) - }) - }.ToJson(); - context.Response.ContentType = MediaTypeNames.Application.Json; - await context.Response.WriteAsync(result); - } - }); - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - app.UseShowAllServicesMiddleware(); - app.UseMigrationsEndPoint(); - app.UseWebAssemblyDebugging(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseBlazorFrameworkFiles(); - app.UseStaticFiles(); - app.UseRouting(); - - app.UseCookiePolicy(); - app.UseAuthentication(); - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}"); - endpoints.MapRazorPages(); - endpoints.MapHealthChecks("home_page_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("homePageHealthCheck") }); - endpoints.MapHealthChecks("api_health_check", new HealthCheckOptions { Predicate = check => check.Tags.Contains("apiHealthCheck") }); - //endpoints.MapBlazorHub("/admin"); - endpoints.MapFallbackToFile("index.html"); - }); - } - -} diff --git a/src/Web/Web.csproj b/src/Web/Web.csproj index 93d6f8229..53b237438 100644 --- a/src/Web/Web.csproj +++ b/src/Web/Web.csproj @@ -2,23 +2,17 @@ net6.0 + enable + enable Microsoft.eShopWeb.Web aspnet-Web2-1FA3F72E-E7E3-4360-9E49-1CCCD7FE85F7 latest - - - - - - - - diff --git a/tests/FunctionalTests/PublicApi/ApiTestFixture.cs b/tests/FunctionalTests/PublicApi/ApiTestFixture.cs index 5435d4330..4513284eb 100644 --- a/tests/FunctionalTests/PublicApi/ApiTestFixture.cs +++ b/tests/FunctionalTests/PublicApi/ApiTestFixture.cs @@ -1,78 +1,43 @@ -using System; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; -using Microsoft.eShopWeb.PublicApi; +using Microsoft.eShopWeb.PublicApi.AuthEndpoints; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Hosting; namespace Microsoft.eShopWeb.FunctionalTests.PublicApi; -public class ApiTestFixture : WebApplicationFactory +public class TestApiApplication : WebApplicationFactory { - protected override void ConfigureWebHost(IWebHostBuilder builder) + private readonly string _environment = "Testing"; + + protected override IHost CreateHost(IHostBuilder builder) { - builder.UseEnvironment("Testing"); + builder.UseEnvironment(_environment); + // Add mock/test services to the builder here builder.ConfigureServices(services => { - services.AddEntityFrameworkInMemoryDatabase(); - - // Create a new service provider. - var provider = services - .AddEntityFrameworkInMemoryDatabase() - .BuildServiceProvider(); - - // Add a database context (ApplicationDbContext) using an in-memory - // database for testing. - services.AddDbContext(options => + services.AddScoped(sp => { - options.UseInMemoryDatabase("InMemoryDbForTesting"); - options.UseInternalServiceProvider(provider); + // Replace SQLite with in-memory database for tests + return new DbContextOptionsBuilder() + .UseInMemoryDatabase("DbForPublicApi") + .UseApplicationServiceProvider(sp) + .Options; }); - - services.AddDbContext(options => + services.AddScoped(sp => { - options.UseInMemoryDatabase("Identity"); - options.UseInternalServiceProvider(provider); + // Replace SQLite with in-memory database for tests + return new DbContextOptionsBuilder() + .UseInMemoryDatabase("IdentityDbForPublicApi") + .UseApplicationServiceProvider(sp) + .Options; }); - - // Build the service provider. - var sp = services.BuildServiceProvider(); - - // Create a scope to obtain a reference to the database - // context (ApplicationDbContext). - using (var scope = sp.CreateScope()) - { - var scopedServices = scope.ServiceProvider; - var db = scopedServices.GetRequiredService(); - var loggerFactory = scopedServices.GetRequiredService(); - - var logger = scopedServices - .GetRequiredService>(); - - // Ensure the database is created. - db.Database.EnsureCreated(); - - try - { - // Seed the database with test data. - CatalogContextSeed.SeedAsync(db, loggerFactory).Wait(); - - // seed sample user data - var userManager = scopedServices.GetRequiredService>(); - var roleManager = scopedServices.GetRequiredService>(); - AppIdentityDbContextSeed.SeedAsync(userManager, roleManager).Wait(); - } - catch (Exception ex) - { - logger.LogError(ex, $"An error occurred seeding the " + - "database with test messages. Error: {ex.Message}"); - } - } }); + + return base.CreateHost(builder); } } diff --git a/tests/FunctionalTests/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs b/tests/FunctionalTests/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs index ead822eba..d695245ba 100644 --- a/tests/FunctionalTests/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs +++ b/tests/FunctionalTests/PublicApi/AuthEndpoints/AuthenticateEndpoint.cs @@ -10,11 +10,11 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class AuthenticateEndpoint : IClassFixture +public class AuthenticateEndpoint : IClassFixture { JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - public AuthenticateEndpoint(ApiTestFixture factory) + public AuthenticateEndpoint(TestApiApplication factory) { Client = factory.CreateClient(); } diff --git a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/ApiCatalogControllerList.cs b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/ApiCatalogControllerList.cs index 514e29e05..1870e731f 100644 --- a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/ApiCatalogControllerList.cs +++ b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/ApiCatalogControllerList.cs @@ -8,9 +8,9 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class ApiCatalogControllerList : IClassFixture +public class ApiCatalogControllerList : IClassFixture { - public ApiCatalogControllerList(ApiTestFixture factory) + public ApiCatalogControllerList(TestApiApplication factory) { Client = factory.CreateClient(); } diff --git a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/CreateEndpoint.cs b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/CreateEndpoint.cs index 855ea3663..3b75095ea 100644 --- a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/CreateEndpoint.cs +++ b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/CreateEndpoint.cs @@ -12,7 +12,7 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class CreateEndpoint : IClassFixture +public class CreateEndpoint : IClassFixture { JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; private int _testBrandId = 1; @@ -21,7 +21,7 @@ public class CreateEndpoint : IClassFixture private string _testName = "test name"; private decimal _testPrice = 1.23m; - public CreateEndpoint(ApiTestFixture factory) + public CreateEndpoint(TestApiApplication factory) { Client = factory.CreateClient(); } diff --git a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/DeleteEndpoint.cs b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/DeleteEndpoint.cs index dde7e280c..5d62e1279 100644 --- a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/DeleteEndpoint.cs +++ b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/DeleteEndpoint.cs @@ -11,11 +11,11 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class DeleteEndpoint : IClassFixture +public class DeleteEndpoint : IClassFixture { JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - public DeleteEndpoint(ApiTestFixture factory) + public DeleteEndpoint(TestApiApplication factory) { Client = factory.CreateClient(); } diff --git a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/GetByIdEndpoint.cs b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/GetByIdEndpoint.cs index c8a3b4070..0d7e1b436 100644 --- a/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/GetByIdEndpoint.cs +++ b/tests/FunctionalTests/PublicApi/CatalogItemEndpoints/GetByIdEndpoint.cs @@ -9,11 +9,11 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class GetByIdEndpoint : IClassFixture +public class GetByIdEndpoint : IClassFixture { JsonSerializerOptions _jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - public GetByIdEndpoint(ApiTestFixture factory) + public GetByIdEndpoint(TestApiApplication factory) { Client = factory.CreateClient(); } diff --git a/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs b/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs index 1625c7fd7..37d8a1a98 100644 --- a/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs +++ b/tests/FunctionalTests/Web/Controllers/AccountControllerSignIn.cs @@ -11,9 +11,9 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class AccountControllerSignIn : IClassFixture +public class AccountControllerSignIn : IClassFixture { - public AccountControllerSignIn(WebTestFixture factory) + public AccountControllerSignIn(TestApplication factory) { Client = factory.CreateClient(new WebApplicationFactoryClientOptions { diff --git a/tests/FunctionalTests/Web/Controllers/CatalogControllerIndex.cs b/tests/FunctionalTests/Web/Controllers/CatalogControllerIndex.cs index c2f26b456..82c804b5c 100644 --- a/tests/FunctionalTests/Web/Controllers/CatalogControllerIndex.cs +++ b/tests/FunctionalTests/Web/Controllers/CatalogControllerIndex.cs @@ -5,9 +5,9 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class CatalogControllerIndex : IClassFixture +public class CatalogControllerIndex : IClassFixture { - public CatalogControllerIndex(WebTestFixture factory) + public CatalogControllerIndex(TestApplication factory) { Client = factory.CreateClient(); } diff --git a/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs b/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs index e739f484b..72d1f23c8 100644 --- a/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs +++ b/tests/FunctionalTests/Web/Controllers/OrderControllerIndex.cs @@ -7,9 +7,9 @@ namespace Microsoft.eShopWeb.FunctionalTests.Web.Controllers; [Collection("Sequential")] -public class OrderIndexOnGet : IClassFixture +public class OrderIndexOnGet : IClassFixture { - public OrderIndexOnGet(WebTestFixture factory) + public OrderIndexOnGet(TestApplication factory) { Client = factory.CreateClient(new WebApplicationFactoryClientOptions { diff --git a/tests/FunctionalTests/Web/Pages/BasketPageCheckout.cs b/tests/FunctionalTests/Web/Pages/BasketPageCheckout.cs index 8014c04b1..202334618 100644 --- a/tests/FunctionalTests/Web/Pages/BasketPageCheckout.cs +++ b/tests/FunctionalTests/Web/Pages/BasketPageCheckout.cs @@ -10,9 +10,9 @@ namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages; [Collection("Sequential")] -public class BasketPageCheckout : IClassFixture +public class BasketPageCheckout : IClassFixture { - public BasketPageCheckout(WebTestFixture factory) + public BasketPageCheckout(TestApplication factory) { Client = factory.CreateClient(new WebApplicationFactoryClientOptions { diff --git a/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs b/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs index 873d3eaa5..76cc135ed 100644 --- a/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs +++ b/tests/FunctionalTests/Web/Pages/HomePageOnGet.cs @@ -6,9 +6,9 @@ namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages; [Collection("Sequential")] -public class HomePageOnGet : IClassFixture +public class HomePageOnGet : IClassFixture { - public HomePageOnGet(WebTestFixture factory) + public HomePageOnGet(TestApplication factory) { Client = factory.CreateClient(); } diff --git a/tests/FunctionalTests/Web/WebTestFixture.cs b/tests/FunctionalTests/Web/WebTestFixture.cs index e7194f751..55b2e7412 100644 --- a/tests/FunctionalTests/Web/WebTestFixture.cs +++ b/tests/FunctionalTests/Web/WebTestFixture.cs @@ -5,74 +5,42 @@ using Microsoft.EntityFrameworkCore; using Microsoft.eShopWeb.Infrastructure.Data; using Microsoft.eShopWeb.Infrastructure.Identity; -using Microsoft.eShopWeb.Web; +using Microsoft.eShopWeb.Web.Interfaces; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; namespace Microsoft.eShopWeb.FunctionalTests.Web; -public class WebTestFixture : WebApplicationFactory +public class TestApplication : WebApplicationFactory { - protected override void ConfigureWebHost(IWebHostBuilder builder) + private readonly string _environment = "Development"; + + protected override IHost CreateHost(IHostBuilder builder) { - builder.UseEnvironment("Testing"); + builder.UseEnvironment(_environment); + // Add mock/test services to the builder here builder.ConfigureServices(services => { - services.AddEntityFrameworkInMemoryDatabase(); - - // Create a new service provider. - var provider = services - .AddEntityFrameworkInMemoryDatabase() - .BuildServiceProvider(); - - // Add a database context (ApplicationDbContext) using an in-memory - // database for testing. - services.AddDbContext(options => + services.AddScoped(sp => { - options.UseInMemoryDatabase("InMemoryDbForTesting"); - options.UseInternalServiceProvider(provider); + // Replace SQLite with in-memory database for tests + return new DbContextOptionsBuilder() + .UseInMemoryDatabase("InMemoryDbForTesting") + .UseApplicationServiceProvider(sp) + .Options; }); - - services.AddDbContext(options => + services.AddScoped(sp => { - options.UseInMemoryDatabase("Identity"); - options.UseInternalServiceProvider(provider); + // Replace SQLite with in-memory database for tests + return new DbContextOptionsBuilder() + .UseInMemoryDatabase("Identity") + .UseApplicationServiceProvider(sp) + .Options; }); - - // Build the service provider. - var sp = services.BuildServiceProvider(); - - // Create a scope to obtain a reference to the database - // context (ApplicationDbContext). - using (var scope = sp.CreateScope()) - { - var scopedServices = scope.ServiceProvider; - var db = scopedServices.GetRequiredService(); - var loggerFactory = scopedServices.GetRequiredService(); - - var logger = scopedServices - .GetRequiredService>(); - - // Ensure the database is created. - db.Database.EnsureCreated(); - - try - { - // Seed the database with test data. - CatalogContextSeed.SeedAsync(db, loggerFactory).Wait(); - - // seed sample user data - var userManager = scopedServices.GetRequiredService>(); - var roleManager = scopedServices.GetRequiredService>(); - AppIdentityDbContextSeed.SeedAsync(userManager, roleManager).Wait(); - } - catch (Exception ex) - { - logger.LogError(ex, $"An error occurred seeding the " + - "database with test messages. Error: {ex.Message}"); - } - } }); + + return base.CreateHost(builder); } }