From 11c26679380f66a13f4ec82b87e702a5cbeb21b9 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 30 Aug 2026 16:09:19 +0200 Subject: [PATCH 1/3] Basic Web API series: migrate all six parts to aspnetcore-webapi/BasicWebApiSeries Brings the six-part ASP.NET Core Web API series over from CodeMazeBlog/.NET-Core-Series into one folder per article, in reading order, with an aggregate solution at the folder root. Folder N+1 is folder N plus that article's work. Database/ (part 1, was Part 1/init.sql) ServiceConfiguration/ (part 2, was Part 2/AccountOwnerServer) Logging/ (part 3) RepositoryPatternWithEfCore/ (part 4) UsingRepositoryForGetRequests/ (part 5) UsingRepositoryForWriteRequests/ (part 6) Database and platform - Part 1's MySQL Workbench script is replaced by a hand-written, re-runnable T-SQL script that creates AccountOwner, both tables, the foreign key and the sample data with the same fixed ids the later parts call endpoints with. Schema.mwb and Schema.mwb.bak are gone with the engine. - Pomelo.EntityFrameworkCore.MySql out, Microsoft.EntityFrameworkCore.SqlServer 10.0.11 in. Nothing calls EnsureCreated(): the script owns the schema, including the explicit column lengths and DATE types EF Core's default model would not produce. - Every project targets net10.0. Applied across all five .NET folders, not just the one article that documents each - ConfigureIISIntegration and its call site removed everywhere. IISOptions is the out-of-process hosting options type and the lambda set nothing. - Pipeline reordered so UseForwardedHeaders runs first; UseStaticFiles dropped (no wwwroot) and the explicit UseDeveloperExceptionPage dropped (WebApplication registers it in Development), keeping UseHsts. - launchSettings.json regenerated to the net10 template's two Project profiles. - NLog host wiring (ClearProviders + UseNLog) carried into parts 4, 5 and 6 as well as 3, so ILogger keeps reaching the file. Verified by running part 6 and reading the log. - nlog.config relative paths in all four folders that carry the file. The committed file pointed at one developer's d: drive and NLog discards silently when the path is unwritable. - NLog.Extensions.Logging 5.3.15 replaced by NLog.Web.AspNetCore 6.2.0. - [Table] casing normalised to the script's identifiers across parts 4 to 6. - "Address cannot be loner then 100 characters" fixed in all five places. Repository and controllers - IRepositoryWrapper.SaveAsync(), async repository reads with ToListAsync and FirstOrDefaultAsync, async actions. FindAll and FindByCondition stay synchronous and keep returning IQueryable; Create, Update and Delete only stage a change. - RepositoryContext takes DbContextOptions; DbSet properties come from Set() instead of being declared nullable. - RepositoryWrapper uses a primary constructor and ??=. - Owner? return types where the query can miss. - try/catch removed from every action; one IExceptionHandler registered in parts 5 and 6. - The unreachable model-state guards removed from the write actions: [ApiController] short-circuits an empty or invalid body with a 400 before the action runs. - OwnerForUpdateDto properties made string?, matching the creation DTO and the entity. - AutoMapper pinned to 14.0.0 in both folders that use it. Tests - UsingRepositoryForWriteRequests/AccountOwnerServer.Tests runs the repository against a real SQL Server started by Testcontainers and seeded with the same init.sql. Where Docker is unavailable the tests report as skipped with a reason rather than passing silently. --- .../BasicWebApiSeries/BasicWebApiSeries.sln | 332 ++++++++++++++++++ .../BasicWebApiSeries/Database/README.md | 38 ++ .../Database/docker-compose.yml | 9 + .../BasicWebApiSeries/Database/init.sql | 76 ++++ .../Logging/AccountOwnerServer.sln | 37 ++ .../AccountOwnerServer.csproj | 13 + .../Controllers/WeatherForecastController.cs | 29 ++ .../Extensions/ServiceExtensions.cs | 25 ++ .../Logging/AccountOwnerServer/Program.cs | 39 ++ .../Properties/launchSettings.json | 23 ++ .../AccountOwnerServer/WeatherForecast.cs | 13 + .../appsettings.Development.json | 8 + .../AccountOwnerServer/appsettings.json | 9 + .../Logging/AccountOwnerServer/nlog.config | 15 + .../Logging/Contracts/Contracts.csproj | 9 + .../Logging/Contracts/ILoggerManager.cs | 10 + .../Logging/LoggerService/LoggerManager.cs | 18 + .../LoggerService/LoggerService.csproj | 17 + aspnetcore-webapi/BasicWebApiSeries/README.md | 37 ++ .../AccountOwnerServer.sln | 49 +++ .../AccountOwnerServer.csproj | 18 + .../Controllers/WeatherForecastController.cs | 30 ++ .../Extensions/ServiceExtensions.cs | 40 +++ .../AccountOwnerServer/Program.cs | 41 +++ .../Properties/launchSettings.json | 23 ++ .../AccountOwnerServer/WeatherForecast.cs | 13 + .../appsettings.Development.json | 8 + .../AccountOwnerServer/appsettings.json | 12 + .../AccountOwnerServer/nlog.config | 15 + .../Contracts/Contracts.csproj | 13 + .../Contracts/IAccountRepository.cs | 8 + .../Contracts/ILoggerManager.cs | 10 + .../Contracts/IOwnerRepository.cs | 8 + .../Contracts/IRepositoryBase.cs | 17 + .../Contracts/IRepositoryWrapper.cs | 9 + .../Entities/Entities.csproj | 13 + .../Entities/Models/Account.cs | 21 ++ .../Entities/Models/Owner.cs | 24 ++ .../Entities/RepositoryContext.cs | 19 + .../LoggerService/LoggerManager.cs | 18 + .../LoggerService/LoggerService.csproj | 17 + .../Repository/AccountRepository.cs | 14 + .../Repository/OwnerRepository.cs | 14 + .../Repository/Repository.csproj | 14 + .../Repository/RepositoryBase.cs | 27 ++ .../Repository/RepositoryWrapper.cs | 18 + .../AccountOwnerServer.sln | 25 ++ .../AccountOwnerServer.csproj | 9 + .../Controllers/WeatherForecastController.cs | 33 ++ .../Extensions/ServiceExtensions.cs | 17 + .../AccountOwnerServer/Program.cs | 32 ++ .../Properties/launchSettings.json | 23 ++ .../AccountOwnerServer/WeatherForecast.cs | 13 + .../appsettings.Development.json | 8 + .../AccountOwnerServer/appsettings.json | 9 + .../AccountOwnerServer.sln | 49 +++ .../AccountOwnerServer.csproj | 19 + .../Controllers/OwnerController.cs | 67 ++++ .../Errors/GlobalExceptionHandler.cs | 29 ++ .../Extensions/ServiceExtensions.cs | 40 +++ .../AccountOwnerServer/MappingProfile.cs | 17 + .../AccountOwnerServer/Program.cs | 49 +++ .../Properties/launchSettings.json | 23 ++ .../appsettings.Development.json | 8 + .../AccountOwnerServer/appsettings.json | 12 + .../AccountOwnerServer/nlog.config | 15 + .../Contracts/Contracts.csproj | 13 + .../Contracts/IAccountRepository.cs | 8 + .../Contracts/ILoggerManager.cs | 10 + .../Contracts/IOwnerRepository.cs | 11 + .../Contracts/IRepositoryBase.cs | 17 + .../Contracts/IRepositoryWrapper.cs | 9 + .../DataTransferObjects/AccountDto.cs | 11 + .../Entities/DataTransferObjects/OwnerDto.cs | 12 + .../Entities/Entities.csproj | 13 + .../Entities/Models/Account.cs | 22 ++ .../Entities/Models/Owner.cs | 25 ++ .../Entities/RepositoryContext.cs | 19 + .../LoggerService/LoggerManager.cs | 18 + .../LoggerService/LoggerService.csproj | 17 + .../Repository/AccountRepository.cs | 14 + .../Repository/OwnerRepository.cs | 31 ++ .../Repository/Repository.csproj | 14 + .../Repository/RepositoryBase.cs | 27 ++ .../Repository/RepositoryWrapper.cs | 18 + .../AccountOwnerServer.Tests.csproj | 31 ++ .../DockerFactAttribute.cs | 47 +++ .../AccountOwnerServer.Tests/GlobalUsings.cs | 1 + .../OwnerRepositoryTests.cs | 75 ++++ .../SqlServerFixture.cs | 57 +++ .../AccountOwnerServer.sln | 107 ++++++ .../AccountOwnerServer.csproj | 19 + .../Controllers/OwnerController.cs | 130 +++++++ .../Errors/GlobalExceptionHandler.cs | 29 ++ .../Extensions/ServiceExtensions.cs | 40 +++ .../AccountOwnerServer/MappingProfile.cs | 21 ++ .../AccountOwnerServer/Program.cs | 49 +++ .../Properties/launchSettings.json | 23 ++ .../appsettings.Development.json | 8 + .../AccountOwnerServer/appsettings.json | 12 + .../AccountOwnerServer/nlog.config | 15 + .../Contracts/Contracts.csproj | 13 + .../Contracts/IAccountRepository.cs | 9 + .../Contracts/ILoggerManager.cs | 10 + .../Contracts/IOwnerRepository.cs | 14 + .../Contracts/IRepositoryBase.cs | 17 + .../Contracts/IRepositoryWrapper.cs | 9 + .../DataTransferObjects/AccountDto.cs | 11 + .../Entities/DataTransferObjects/OwnerDto.cs | 12 + .../OwnerForCreationDto.cs | 18 + .../DataTransferObjects/OwnerForUpdateDto.cs | 18 + .../Entities/Entities.csproj | 13 + .../Entities/Models/Account.cs | 22 ++ .../Entities/Models/Owner.cs | 25 ++ .../Entities/RepositoryContext.cs | 19 + .../LoggerService/LoggerManager.cs | 18 + .../LoggerService/LoggerService.csproj | 17 + .../Repository/AccountRepository.cs | 18 + .../Repository/OwnerRepository.cs | 39 ++ .../Repository/Repository.csproj | 14 + .../Repository/RepositoryBase.cs | 27 ++ .../Repository/RepositoryWrapper.cs | 18 + 122 files changed, 3101 insertions(+) create mode 100644 aspnetcore-webapi/BasicWebApiSeries/BasicWebApiSeries.sln create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Database/README.md create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Database/docker-compose.yml create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Database/init.sql create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer.sln create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/AccountOwnerServer.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastController.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Program.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/WeatherForecast.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.Development.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/nlog.config create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/Contracts.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/ILoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerService.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/README.md create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer.sln create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/AccountOwnerServer.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Controllers/WeatherForecastController.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Program.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/WeatherForecast.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.Development.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/nlog.config create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/Contracts.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IAccountRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/ILoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IOwnerRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryBase.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryWrapper.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Entities.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Account.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Owner.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/RepositoryContext.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerService.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/AccountRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/OwnerRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/Repository.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryBase.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryWrapper.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer.sln create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/AccountOwnerServer.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Controllers/WeatherForecastController.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Program.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/WeatherForecast.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.Development.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer.sln create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/AccountOwnerServer.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Controllers/OwnerController.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/MappingProfile.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Program.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.Development.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/nlog.config create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/Contracts.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IAccountRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/ILoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IOwnerRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryBase.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryWrapper.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/AccountDto.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/OwnerDto.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Entities.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Account.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Owner.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/RepositoryContext.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerService.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/AccountRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/OwnerRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/Repository.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryBase.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryWrapper.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/AccountOwnerServer.Tests.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/DockerFactAttribute.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/GlobalUsings.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/OwnerRepositoryTests.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/SqlServerFixture.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.sln create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/AccountOwnerServer.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Controllers/OwnerController.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/MappingProfile.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Program.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Properties/launchSettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.Development.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.json create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/nlog.config create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/Contracts.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IAccountRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/ILoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IOwnerRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryBase.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryWrapper.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/AccountDto.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerDto.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForCreationDto.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForUpdateDto.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Entities.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Account.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Owner.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/RepositoryContext.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerManager.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerService.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/AccountRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/OwnerRepository.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/Repository.csproj create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryBase.cs create mode 100644 aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryWrapper.cs diff --git a/aspnetcore-webapi/BasicWebApiSeries/BasicWebApiSeries.sln b/aspnetcore-webapi/BasicWebApiSeries/BasicWebApiSeries.sln new file mode 100644 index 0000000000..6f6cf056b1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/BasicWebApiSeries.sln @@ -0,0 +1,332 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ServiceConfiguration", "ServiceConfiguration", "{D9D1C010-0649-F69F-F2F1-9505732C8135}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "ServiceConfiguration\AccountOwnerServer\AccountOwnerServer.csproj", "{59D4C354-9CA9-43FB-8236-D4F8034E5BB2}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Logging", "Logging", "{592BFD43-8236-1486-20CA-62D2F7ED4E57}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "Logging\AccountOwnerServer\AccountOwnerServer.csproj", "{3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "Logging\LoggerService\LoggerService.csproj", "{348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Logging\Contracts\Contracts.csproj", "{F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "RepositoryPatternWithEfCore", "RepositoryPatternWithEfCore", "{225D120C-FE9E-56C9-082E-4E9BEFF1B9C1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "RepositoryPatternWithEfCore\AccountOwnerServer\AccountOwnerServer.csproj", "{2717507B-8A1F-4E89-8DED-76A6ADB79E61}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "RepositoryPatternWithEfCore\LoggerService\LoggerService.csproj", "{56FFE5D0-878F-4D18-848D-B7457B8ECA2D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "RepositoryPatternWithEfCore\Contracts\Contracts.csproj", "{5F023375-15C2-4843-A38E-78C97A9AA0DD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "RepositoryPatternWithEfCore\Entities\Entities.csproj", "{2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "RepositoryPatternWithEfCore\Repository\Repository.csproj", "{1C5636BE-7C1A-4512-9064-712C3B48ADD8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UsingRepositoryForGetRequests", "UsingRepositoryForGetRequests", "{8C336C94-9995-2443-DFAD-0DBDC4FD8F35}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "UsingRepositoryForGetRequests\AccountOwnerServer\AccountOwnerServer.csproj", "{B75E5A35-95B2-4913-B012-8BEEF83AAB06}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "UsingRepositoryForGetRequests\LoggerService\LoggerService.csproj", "{FF83F444-50D1-47A6-A367-FF9B502D1E4A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "UsingRepositoryForGetRequests\Contracts\Contracts.csproj", "{30476240-59E2-4280-BA03-59764CE15E67}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "UsingRepositoryForGetRequests\Entities\Entities.csproj", "{F435C953-2490-47A2-9A41-D6FAD7C10F80}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "UsingRepositoryForGetRequests\Repository\Repository.csproj", "{9EA34724-1DA9-49B6-B8FF-6929752226FA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UsingRepositoryForWriteRequests", "UsingRepositoryForWriteRequests", "{A2DD47B0-FD7E-9A5D-E48B-2546E6692334}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "UsingRepositoryForWriteRequests\AccountOwnerServer\AccountOwnerServer.csproj", "{DC440D6F-7F79-4D4A-82FD-C566AEB168E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "UsingRepositoryForWriteRequests\LoggerService\LoggerService.csproj", "{558A5044-88C7-469C-B2BD-01FED3C8CC49}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "UsingRepositoryForWriteRequests\Contracts\Contracts.csproj", "{3F67E84F-6B51-4688-8434-7BA718C17857}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "UsingRepositoryForWriteRequests\Entities\Entities.csproj", "{769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "UsingRepositoryForWriteRequests\Repository\Repository.csproj", "{CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "UsingRepositoryForWriteRequests\AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{EB2FFD0C-B624-4F50-860C-D54D7284408F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Debug|x64.ActiveCfg = Debug|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Debug|x64.Build.0 = Debug|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Debug|x86.ActiveCfg = Debug|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Debug|x86.Build.0 = Debug|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Release|Any CPU.Build.0 = Release|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Release|x64.ActiveCfg = Release|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Release|x64.Build.0 = Release|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Release|x86.ActiveCfg = Release|Any CPU + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2}.Release|x86.Build.0 = Release|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Debug|x64.ActiveCfg = Debug|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Debug|x64.Build.0 = Debug|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Debug|x86.ActiveCfg = Debug|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Debug|x86.Build.0 = Debug|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Release|Any CPU.Build.0 = Release|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Release|x64.ActiveCfg = Release|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Release|x64.Build.0 = Release|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Release|x86.ActiveCfg = Release|Any CPU + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F}.Release|x86.Build.0 = Release|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Debug|x64.ActiveCfg = Debug|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Debug|x64.Build.0 = Debug|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Debug|x86.ActiveCfg = Debug|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Debug|x86.Build.0 = Debug|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Release|Any CPU.Build.0 = Release|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Release|x64.ActiveCfg = Release|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Release|x64.Build.0 = Release|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Release|x86.ActiveCfg = Release|Any CPU + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5}.Release|x86.Build.0 = Release|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Debug|x64.ActiveCfg = Debug|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Debug|x64.Build.0 = Debug|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Debug|x86.ActiveCfg = Debug|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Debug|x86.Build.0 = Debug|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Release|Any CPU.Build.0 = Release|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Release|x64.ActiveCfg = Release|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Release|x64.Build.0 = Release|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Release|x86.ActiveCfg = Release|Any CPU + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA}.Release|x86.Build.0 = Release|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Debug|x64.ActiveCfg = Debug|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Debug|x64.Build.0 = Debug|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Debug|x86.ActiveCfg = Debug|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Debug|x86.Build.0 = Debug|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Release|Any CPU.Build.0 = Release|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Release|x64.ActiveCfg = Release|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Release|x64.Build.0 = Release|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Release|x86.ActiveCfg = Release|Any CPU + {2717507B-8A1F-4E89-8DED-76A6ADB79E61}.Release|x86.Build.0 = Release|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Debug|x64.ActiveCfg = Debug|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Debug|x64.Build.0 = Debug|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Debug|x86.ActiveCfg = Debug|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Debug|x86.Build.0 = Debug|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Release|Any CPU.Build.0 = Release|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Release|x64.ActiveCfg = Release|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Release|x64.Build.0 = Release|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Release|x86.ActiveCfg = Release|Any CPU + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D}.Release|x86.Build.0 = Release|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Debug|x64.Build.0 = Debug|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Debug|x86.ActiveCfg = Debug|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Debug|x86.Build.0 = Debug|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Release|Any CPU.Build.0 = Release|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Release|x64.ActiveCfg = Release|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Release|x64.Build.0 = Release|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Release|x86.ActiveCfg = Release|Any CPU + {5F023375-15C2-4843-A38E-78C97A9AA0DD}.Release|x86.Build.0 = Release|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Debug|x64.ActiveCfg = Debug|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Debug|x64.Build.0 = Debug|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Debug|x86.Build.0 = Debug|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Release|Any CPU.Build.0 = Release|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Release|x64.ActiveCfg = Release|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Release|x64.Build.0 = Release|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Release|x86.ActiveCfg = Release|Any CPU + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B}.Release|x86.Build.0 = Release|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Debug|x64.ActiveCfg = Debug|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Debug|x64.Build.0 = Debug|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Debug|x86.ActiveCfg = Debug|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Debug|x86.Build.0 = Debug|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Release|Any CPU.Build.0 = Release|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Release|x64.ActiveCfg = Release|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Release|x64.Build.0 = Release|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Release|x86.ActiveCfg = Release|Any CPU + {1C5636BE-7C1A-4512-9064-712C3B48ADD8}.Release|x86.Build.0 = Release|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Debug|x64.ActiveCfg = Debug|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Debug|x64.Build.0 = Debug|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Debug|x86.ActiveCfg = Debug|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Debug|x86.Build.0 = Debug|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Release|Any CPU.Build.0 = Release|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Release|x64.ActiveCfg = Release|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Release|x64.Build.0 = Release|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Release|x86.ActiveCfg = Release|Any CPU + {B75E5A35-95B2-4913-B012-8BEEF83AAB06}.Release|x86.Build.0 = Release|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Debug|x64.ActiveCfg = Debug|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Debug|x64.Build.0 = Debug|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Debug|x86.ActiveCfg = Debug|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Debug|x86.Build.0 = Debug|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Release|Any CPU.Build.0 = Release|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Release|x64.ActiveCfg = Release|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Release|x64.Build.0 = Release|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Release|x86.ActiveCfg = Release|Any CPU + {FF83F444-50D1-47A6-A367-FF9B502D1E4A}.Release|x86.Build.0 = Release|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Debug|x64.ActiveCfg = Debug|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Debug|x64.Build.0 = Debug|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Debug|x86.ActiveCfg = Debug|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Debug|x86.Build.0 = Debug|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Release|Any CPU.Build.0 = Release|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Release|x64.ActiveCfg = Release|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Release|x64.Build.0 = Release|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Release|x86.ActiveCfg = Release|Any CPU + {30476240-59E2-4280-BA03-59764CE15E67}.Release|x86.Build.0 = Release|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Debug|x64.ActiveCfg = Debug|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Debug|x64.Build.0 = Debug|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Debug|x86.ActiveCfg = Debug|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Debug|x86.Build.0 = Debug|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Release|Any CPU.Build.0 = Release|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Release|x64.ActiveCfg = Release|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Release|x64.Build.0 = Release|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Release|x86.ActiveCfg = Release|Any CPU + {F435C953-2490-47A2-9A41-D6FAD7C10F80}.Release|x86.Build.0 = Release|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Debug|x64.ActiveCfg = Debug|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Debug|x64.Build.0 = Debug|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Debug|x86.ActiveCfg = Debug|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Debug|x86.Build.0 = Debug|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Release|Any CPU.Build.0 = Release|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Release|x64.ActiveCfg = Release|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Release|x64.Build.0 = Release|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Release|x86.ActiveCfg = Release|Any CPU + {9EA34724-1DA9-49B6-B8FF-6929752226FA}.Release|x86.Build.0 = Release|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Debug|x64.ActiveCfg = Debug|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Debug|x64.Build.0 = Debug|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Debug|x86.ActiveCfg = Debug|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Debug|x86.Build.0 = Debug|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Release|Any CPU.Build.0 = Release|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Release|x64.ActiveCfg = Release|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Release|x64.Build.0 = Release|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Release|x86.ActiveCfg = Release|Any CPU + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5}.Release|x86.Build.0 = Release|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Debug|x64.ActiveCfg = Debug|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Debug|x64.Build.0 = Debug|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Debug|x86.ActiveCfg = Debug|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Debug|x86.Build.0 = Debug|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Release|Any CPU.ActiveCfg = Release|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Release|Any CPU.Build.0 = Release|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Release|x64.ActiveCfg = Release|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Release|x64.Build.0 = Release|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Release|x86.ActiveCfg = Release|Any CPU + {558A5044-88C7-469C-B2BD-01FED3C8CC49}.Release|x86.Build.0 = Release|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Debug|x64.ActiveCfg = Debug|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Debug|x64.Build.0 = Debug|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Debug|x86.Build.0 = Debug|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Release|Any CPU.Build.0 = Release|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Release|x64.ActiveCfg = Release|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Release|x64.Build.0 = Release|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Release|x86.ActiveCfg = Release|Any CPU + {3F67E84F-6B51-4688-8434-7BA718C17857}.Release|x86.Build.0 = Release|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Debug|x64.ActiveCfg = Debug|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Debug|x64.Build.0 = Debug|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Debug|x86.ActiveCfg = Debug|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Debug|x86.Build.0 = Debug|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Release|Any CPU.Build.0 = Release|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Release|x64.ActiveCfg = Release|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Release|x64.Build.0 = Release|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Release|x86.ActiveCfg = Release|Any CPU + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5}.Release|x86.Build.0 = Release|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Debug|x64.ActiveCfg = Debug|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Debug|x64.Build.0 = Debug|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Debug|x86.ActiveCfg = Debug|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Debug|x86.Build.0 = Debug|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Release|Any CPU.Build.0 = Release|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Release|x64.ActiveCfg = Release|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Release|x64.Build.0 = Release|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Release|x86.ActiveCfg = Release|Any CPU + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5}.Release|x86.Build.0 = Release|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Debug|x64.ActiveCfg = Debug|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Debug|x64.Build.0 = Debug|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Debug|x86.ActiveCfg = Debug|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Debug|x86.Build.0 = Debug|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Release|Any CPU.Build.0 = Release|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Release|x64.ActiveCfg = Release|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Release|x64.Build.0 = Release|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Release|x86.ActiveCfg = Release|Any CPU + {EB2FFD0C-B624-4F50-860C-D54D7284408F}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {59D4C354-9CA9-43FB-8236-D4F8034E5BB2} = {D9D1C010-0649-F69F-F2F1-9505732C8135} + {3BBF1DF5-1A13-4E68-90D2-5186CEE7173F} = {592BFD43-8236-1486-20CA-62D2F7ED4E57} + {348F08A4-6FC5-418D-B28A-33DC6AEBBDE5} = {592BFD43-8236-1486-20CA-62D2F7ED4E57} + {F38C5C71-C966-45DA-80C7-ECCF9A73D4CA} = {592BFD43-8236-1486-20CA-62D2F7ED4E57} + {2717507B-8A1F-4E89-8DED-76A6ADB79E61} = {225D120C-FE9E-56C9-082E-4E9BEFF1B9C1} + {56FFE5D0-878F-4D18-848D-B7457B8ECA2D} = {225D120C-FE9E-56C9-082E-4E9BEFF1B9C1} + {5F023375-15C2-4843-A38E-78C97A9AA0DD} = {225D120C-FE9E-56C9-082E-4E9BEFF1B9C1} + {2F32BCD7-09FD-402C-ABE9-46F8ACF42C3B} = {225D120C-FE9E-56C9-082E-4E9BEFF1B9C1} + {1C5636BE-7C1A-4512-9064-712C3B48ADD8} = {225D120C-FE9E-56C9-082E-4E9BEFF1B9C1} + {B75E5A35-95B2-4913-B012-8BEEF83AAB06} = {8C336C94-9995-2443-DFAD-0DBDC4FD8F35} + {FF83F444-50D1-47A6-A367-FF9B502D1E4A} = {8C336C94-9995-2443-DFAD-0DBDC4FD8F35} + {30476240-59E2-4280-BA03-59764CE15E67} = {8C336C94-9995-2443-DFAD-0DBDC4FD8F35} + {F435C953-2490-47A2-9A41-D6FAD7C10F80} = {8C336C94-9995-2443-DFAD-0DBDC4FD8F35} + {9EA34724-1DA9-49B6-B8FF-6929752226FA} = {8C336C94-9995-2443-DFAD-0DBDC4FD8F35} + {DC440D6F-7F79-4D4A-82FD-C566AEB168E5} = {A2DD47B0-FD7E-9A5D-E48B-2546E6692334} + {558A5044-88C7-469C-B2BD-01FED3C8CC49} = {A2DD47B0-FD7E-9A5D-E48B-2546E6692334} + {3F67E84F-6B51-4688-8434-7BA718C17857} = {A2DD47B0-FD7E-9A5D-E48B-2546E6692334} + {769D39CF-30EA-4BA8-BC9D-78C12C55F0F5} = {A2DD47B0-FD7E-9A5D-E48B-2546E6692334} + {CA5F716F-16C7-4E44-BCA9-7D9ED65BDCC5} = {A2DD47B0-FD7E-9A5D-E48B-2546E6692334} + {EB2FFD0C-B624-4F50-860C-D54D7284408F} = {A2DD47B0-FD7E-9A5D-E48B-2546E6692334} + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/BasicWebApiSeries/Database/README.md b/aspnetcore-webapi/BasicWebApiSeries/Database/README.md new file mode 100644 index 0000000000..b5136f15f4 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Database/README.md @@ -0,0 +1,38 @@ +# Database + +Part 1 of the ASP.NET Core Web API series. This folder holds the SQL Server schema the +rest of the series reads and writes, and nothing else: there is no .NET project here. + +`init.sql` creates the `AccountOwner` database, the `Owner` and `Account` tables, the +foreign key between them, and the sample data. It is re-runnable. + +## Windows, on LocalDB + +``` +sqlcmd -S "(localdb)\MSSQLLocalDB" -i init.sql +``` + +LocalDB comes with the Visual Studio Installer's *Data storage and processing* workload +(and with SQL Server Express media). It is not part of the .NET SDK. + +## Anywhere, in a container + +``` +docker compose up -d +sqlcmd -S localhost -U sa -P "YourStrong!Passw0rd" -C -i init.sql +``` + +`-C` trusts the container's self-signed certificate, which ODBC Driver 18 and the newer +`sqlcmd` builds require. + +## Why the ids are fixed + +The sample data hardcodes its GUIDs. Later parts of the series call endpoints with these +exact ids, so a reader who generates fresh ones cannot follow along. + +## Why nothing calls EnsureCreated() + +The schema comes from this script, with explicit lengths (`NVARCHAR(60)`, `NVARCHAR(100)`, +`NVARCHAR(45)`) and `DATE` columns. EF Core's default model for the same entities would +produce `nvarchar(max)` and `datetime2` instead. The script is the only thing that creates +the schema, in every folder of this series. diff --git a/aspnetcore-webapi/BasicWebApiSeries/Database/docker-compose.yml b/aspnetcore-webapi/BasicWebApiSeries/Database/docker-compose.yml new file mode 100644 index 0000000000..7a2ea64c02 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Database/docker-compose.yml @@ -0,0 +1,9 @@ +services: + sqlserver: + image: mcr.microsoft.com/mssql/server:2022-latest + container_name: accountowner-sqlserver + environment: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: "YourStrong!Passw0rd" + ports: + - "1433:1433" diff --git a/aspnetcore-webapi/BasicWebApiSeries/Database/init.sql b/aspnetcore-webapi/BasicWebApiSeries/Database/init.sql new file mode 100644 index 0000000000..aa84331898 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Database/init.sql @@ -0,0 +1,76 @@ +/* + AccountOwner sample database - ASP.NET Core Web API series, part 1. + + Creates the AccountOwner database, the Owner and Account tables, the foreign key + between them, and the sample data the rest of the series calls its endpoints with. + + The script is re-runnable: it drops the tables before it creates them, so running + it a second time rebuilds the schema instead of failing on names that already exist. + + LocalDB (Windows): + sqlcmd -S "(localdb)\MSSQLLocalDB" -i init.sql + + Container (any OS), started with the docker-compose.yml beside this file: + sqlcmd -S localhost -U sa -P "" -C -i init.sql +*/ + +IF DB_ID('AccountOwner') IS NULL + CREATE DATABASE [AccountOwner]; +GO + +USE [AccountOwner]; +GO + +-- Account first: it holds the foreign key, so it has to go before its parent. +DROP TABLE IF EXISTS [dbo].[Account]; +DROP TABLE IF EXISTS [dbo].[Owner]; +GO + +CREATE TABLE [dbo].[Owner] +( + [OwnerId] UNIQUEIDENTIFIER NOT NULL, + [Name] NVARCHAR(60) NOT NULL, + [DateOfBirth] DATE NOT NULL, + [Address] NVARCHAR(100) NOT NULL, + CONSTRAINT [PK_Owner] PRIMARY KEY CLUSTERED ([OwnerId]) +); +GO + +CREATE TABLE [dbo].[Account] +( + [AccountId] UNIQUEIDENTIFIER NOT NULL, + [DateCreated] DATE NOT NULL, + [AccountType] NVARCHAR(45) NOT NULL, + [OwnerId] UNIQUEIDENTIFIER NOT NULL, + CONSTRAINT [PK_Account] PRIMARY KEY CLUSTERED ([AccountId]), + CONSTRAINT [FK_Account_Owner] FOREIGN KEY ([OwnerId]) + REFERENCES [dbo].[Owner] ([OwnerId]) + ON UPDATE CASCADE + ON DELETE NO ACTION +); +GO + +CREATE INDEX [IX_Account_OwnerId] ON [dbo].[Account] ([OwnerId]); +GO + +-- Owners first. Every Account row names an owner, and the foreign key rejects a row +-- whose owner does not exist yet. +INSERT INTO [dbo].[Owner] ([OwnerId], [Name], [DateOfBirth], [Address]) +VALUES + ('24fd81f8-d58a-4bcc-9f35-dc6cd5641906', N'John Keen', '1980-12-05', N'61 Wellfield Road'), + ('261e1685-cf26-494c-b17c-3546e65f5620', N'Anna Bosh', '1974-11-14', N'27 Colored Row'), + ('a3c1880c-674c-4d18-8f91-5d3608a2c937', N'Sam Query', '1990-04-22', N'91 Western Roads'), + ('f98e4d74-0f68-4aac-89fd-047f1aaca6b6', N'Martin Miller', '1983-05-21', N'3 Edgar Buildings'); +GO + +INSERT INTO [dbo].[Account] ([AccountId], [DateCreated], [AccountType], [OwnerId]) +VALUES + ('03e91478-5608-4132-a753-d494dafce00b', '2003-12-15', N'Domestic', 'f98e4d74-0f68-4aac-89fd-047f1aaca6b6'), + ('356a5a9b-64bf-4de0-bc84-5395a1fdc9c4', '1996-02-15', N'Domestic', '261e1685-cf26-494c-b17c-3546e65f5620'), + ('371b93f2-f8c5-4a32-894a-fc672741aa5b', '1999-05-04', N'Domestic', '24fd81f8-d58a-4bcc-9f35-dc6cd5641906'), + ('670775db-ecc0-4b90-a9ab-37cd0d8e2801', '1999-12-21', N'Savings', '24fd81f8-d58a-4bcc-9f35-dc6cd5641906'), + ('a3fbad0b-7f48-4feb-8ac0-6d3bbc997bfc', '2010-05-28', N'Domestic', 'a3c1880c-674c-4d18-8f91-5d3608a2c937'), + ('aa15f658-04bb-4f73-82af-82db49d0fbef', '1999-05-12', N'Foreign', '24fd81f8-d58a-4bcc-9f35-dc6cd5641906'), + ('c6066eb0-53ca-43e1-97aa-3c2169eec659', '1996-02-16', N'Foreign', '261e1685-cf26-494c-b17c-3546e65f5620'), + ('eccadf79-85fe-402f-893c-32d3f03ed9b1', '2010-06-20', N'Foreign', 'a3c1880c-674c-4d18-8f91-5d3608a2c937'); +GO diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer.sln b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer.sln new file mode 100644 index 0000000000..5696d27b0e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{0213FB50-5C53-4C34-8B2C-E515C7DC2579}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Contracts\Contracts.csproj", "{B731D3C2-9A89-40E4-B96B-AA300B4195BB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LoggerService", "LoggerService\LoggerService.csproj", "{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.Build.0 = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.Build.0 = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8E1F895-37AB-455B-8D05-ED495BE48F88} + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/AccountOwnerServer.csproj b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/AccountOwnerServer.csproj new file mode 100644 index 0000000000..1ad4397b61 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/AccountOwnerServer.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastController.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000000..37ff866619 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastController.cs @@ -0,0 +1,29 @@ +using Contracts; +using Microsoft.AspNetCore.Mvc; + +namespace AccountOwnerServer.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private readonly ILoggerManager _logger; + + public WeatherForecastController(ILoggerManager logger) + { + _logger = logger; + } + + // One request produces one line per level in logs/_logfile.txt. + [HttpGet] + public IEnumerable Get() + { + _logger.LogInfo("Here is info message from the controller."); + _logger.LogDebug("Here is debug message from the controller."); + _logger.LogWarn("Here is warn message from the controller."); + _logger.LogError("Here is error message from the controller."); + + return ["value1", "value2"]; + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000000..061ab690cd --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -0,0 +1,25 @@ +using Contracts; +using LoggerService; + +namespace AccountOwnerServer.Extensions +{ + public static class ServiceExtensions + { + public static void ConfigureCors(this IServiceCollection services) + { + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + }); + } + + + public static void ConfigureLoggerService(this IServiceCollection services) + { + services.AddSingleton(); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Program.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Program.cs new file mode 100644 index 0000000000..83ca7ddf96 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Program.cs @@ -0,0 +1,39 @@ +using AccountOwnerServer.Extensions; +using Microsoft.AspNetCore.HttpOverrides; +using NLog.Web; + +var builder = WebApplication.CreateBuilder(args); + +// NLog is registered as a logging provider, so ILogger reaches the file targets in +// nlog.config as well as ILoggerManager. NLog finds nlog.config in the output directory +// on its own; there is nothing to load by hand. +builder.Logging.ClearProviders(); +builder.Host.UseNLog(); + +builder.Services.ConfigureCors(); +builder.Services.ConfigureLoggerService(); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +// UseForwardedHeaders goes first: every component after it has to see the client's +// scheme and address, not the proxy's, or an HTTPS redirect behind a proxy loops. +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.All +}); + +if (!app.Environment.IsDevelopment()) + app.UseHsts(); + +app.UseHttpsRedirection(); + +app.UseCors("CorsPolicy"); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Properties/launchSettings.json b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Properties/launchSettings.json new file mode 100644 index 0000000000..c2d94ddfd1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/WeatherForecast.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/WeatherForecast.cs new file mode 100644 index 0000000000..41db3296b6 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace AccountOwnerServer +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.Development.json b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.json b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.json new file mode 100644 index 0000000000..10f68b8c8b --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/nlog.config b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/nlog.config new file mode 100644 index 0000000000..225d1ebc2c --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/nlog.config @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/Contracts.csproj b/aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/Contracts.csproj new file mode 100644 index 0000000000..9ed914b5ba --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/Contracts.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/ILoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/ILoggerManager.cs new file mode 100644 index 0000000000..ee1f08105d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/Contracts/ILoggerManager.cs @@ -0,0 +1,10 @@ +namespace Contracts +{ + public interface ILoggerManager + { + void LogInfo(string message); + void LogWarn(string message); + void LogDebug(string message); + void LogError(string message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerManager.cs new file mode 100644 index 0000000000..d776ec8609 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerManager.cs @@ -0,0 +1,18 @@ +using Contracts; +using NLog; + +namespace LoggerService +{ + public class LoggerManager : ILoggerManager + { + private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); + + public void LogDebug(string message) => Logger.Debug(message); + + public void LogError(string message) => Logger.Error(message); + + public void LogInfo(string message) => Logger.Info(message); + + public void LogWarn(string message) => Logger.Warn(message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerService.csproj b/aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerService.csproj new file mode 100644 index 0000000000..049803efdc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/LoggerService/LoggerService.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/README.md b/aspnetcore-webapi/BasicWebApiSeries/README.md new file mode 100644 index 0000000000..f0dffd6b9c --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/README.md @@ -0,0 +1,37 @@ +# Basic Web API Series + +The six-part ASP.NET Core Web API series on Code Maze, one folder per article, in reading +order. Each folder is the previous folder plus that article's work, so a reader can start +anywhere and diff forward. + +| # | Folder | Article | +|---|---|---| +| 1 | [`Database`](Database) | SQL Server With ASP.NET Core: Creating the Database | +| 2 | [`ServiceConfiguration`](ServiceConfiguration) | Service Configuration in ASP.NET Core Web API With Extension Methods | +| 3 | [`Logging`](Logging) | NLog in ASP.NET Core Web API: Logging to a File | +| 4 | [`RepositoryPatternWithEfCore`](RepositoryPatternWithEfCore) | Repository Pattern in .NET Core Web API | +| 5 | [`UsingRepositoryForGetRequests`](UsingRepositoryForGetRequests) | ASP.NET Core Web API GET Requests With a Repository | +| 6 | [`UsingRepositoryForWriteRequests`](UsingRepositoryForWriteRequests) | POST, PUT, and DELETE in ASP.NET Core Web API | + +`BasicWebApiSeries.sln` at this level builds and tests all of them at once. Each article +folder also has its own solution for working through one part on its own. + +## Before you run anything from part 4 onward + +Create the database first. `Database/init.sql` creates `AccountOwner`, both tables and the +sample data; `Database/README.md` has the LocalDB and container commands. The connection +string in each `appsettings.json` points at LocalDB by default. + +Nothing in this series calls `EnsureCreated()`. The script owns the schema, with the +explicit column lengths and `DATE` types the article documents. + +## Everything targets net10.0 + +`Microsoft.EntityFrameworkCore.SqlServer` 10.0.11 where a project touches the database, +`NLog.Web.AspNetCore` 6.2.0 for logging, `AutoMapper` 14.0.0 from part 5 onward. + +## Tests + +`UsingRepositoryForWriteRequests/AccountOwnerServer.Tests` runs the repository against a +real SQL Server in a Testcontainers container, seeded with the same `init.sql`. Where +Docker is not available the tests report as **skipped with a reason**, never as passed. diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer.sln b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer.sln new file mode 100644 index 0000000000..78fe059311 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{0213FB50-5C53-4C34-8B2C-E515C7DC2579}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Contracts", "Contracts\Contracts.csproj", "{B731D3C2-9A89-40E4-B96B-AA300B4195BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoggerService", "LoggerService\LoggerService.csproj", "{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Entities\Entities.csproj", "{16AAE663-A20E-493D-9087-F4F4016F6216}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Repository\Repository.csproj", "{A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.Build.0 = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.Build.0 = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.Build.0 = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|Any CPU.Build.0 = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8E1F895-37AB-455B-8D05-ED495BE48F88} + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/AccountOwnerServer.csproj b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/AccountOwnerServer.csproj new file mode 100644 index 0000000000..c1e042b7eb --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/AccountOwnerServer.csproj @@ -0,0 +1,18 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Controllers/WeatherForecastController.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000000..3731852033 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Controllers/WeatherForecastController.cs @@ -0,0 +1,30 @@ +using Contracts; +using Microsoft.AspNetCore.Mvc; + +namespace AccountOwnerServer.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private readonly IRepositoryWrapper _repository; + + public WeatherForecastController(IRepositoryWrapper repository) + { + _repository = repository; + } + + // Proof that the wrapper resolves and both repositories reach the database. + // Part 5 replaces this with a real OwnerController. + [HttpGet] + public IActionResult Get() + { + var domesticAccounts = _repository.Account + .FindByCondition(a => a.AccountType == "Domestic") + .Count(); + var owners = _repository.Owner.FindAll().Count(); + + return Ok(new { owners, domesticAccounts }); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000000..930c501f22 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -0,0 +1,40 @@ +using Contracts; +using LoggerService; +using Microsoft.EntityFrameworkCore; +using Entities; +using Repository; + +namespace AccountOwnerServer.Extensions +{ + public static class ServiceExtensions + { + public static void ConfigureCors(this IServiceCollection services) + { + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + }); + } + + + public static void ConfigureLoggerService(this IServiceCollection services) + { + services.AddSingleton(); + } + + public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration config) + { + var connectionString = config.GetConnectionString("SqlConnection"); + + services.AddDbContext(o => o.UseSqlServer(connectionString)); + } + + public static void ConfigureRepositoryWrapper(this IServiceCollection services) + { + services.AddScoped(); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Program.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Program.cs new file mode 100644 index 0000000000..3db4eaf38c --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Program.cs @@ -0,0 +1,41 @@ +using AccountOwnerServer.Extensions; +using Microsoft.AspNetCore.HttpOverrides; +using NLog.Web; + +var builder = WebApplication.CreateBuilder(args); + +// NLog is registered as a logging provider, so ILogger reaches the file targets in +// nlog.config as well as ILoggerManager. NLog finds nlog.config in the output directory +// on its own; there is nothing to load by hand. +builder.Logging.ClearProviders(); +builder.Host.UseNLog(); + +builder.Services.ConfigureCors(); +builder.Services.ConfigureLoggerService(); +builder.Services.ConfigureSqlContext(builder.Configuration); +builder.Services.ConfigureRepositoryWrapper(); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +// UseForwardedHeaders goes first: every component after it has to see the client's +// scheme and address, not the proxy's, or an HTTPS redirect behind a proxy loops. +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.All +}); + +if (!app.Environment.IsDevelopment()) + app.UseHsts(); + +app.UseHttpsRedirection(); + +app.UseCors("CorsPolicy"); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Properties/launchSettings.json b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Properties/launchSettings.json new file mode 100644 index 0000000000..c2d94ddfd1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/WeatherForecast.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/WeatherForecast.cs new file mode 100644 index 0000000000..41db3296b6 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace AccountOwnerServer +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.Development.json b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.json b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.json new file mode 100644 index 0000000000..939dc1a951 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "SqlConnection": "Server=(localdb)\\MSSQLLocalDB;Database=AccountOwner;Trusted_Connection=True;TrustServerCertificate=True;" + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/nlog.config b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/nlog.config new file mode 100644 index 0000000000..225d1ebc2c --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/nlog.config @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/Contracts.csproj b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/Contracts.csproj new file mode 100644 index 0000000000..6633e14a8e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/Contracts.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IAccountRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IAccountRepository.cs new file mode 100644 index 0000000000..4286b58adf --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IAccountRepository.cs @@ -0,0 +1,8 @@ +using Entities.Models; + +namespace Contracts +{ + public interface IAccountRepository : IRepositoryBase + { + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/ILoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/ILoggerManager.cs new file mode 100644 index 0000000000..ee1f08105d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/ILoggerManager.cs @@ -0,0 +1,10 @@ +namespace Contracts +{ + public interface ILoggerManager + { + void LogInfo(string message); + void LogWarn(string message); + void LogDebug(string message); + void LogError(string message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IOwnerRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IOwnerRepository.cs new file mode 100644 index 0000000000..0aa0d07b93 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IOwnerRepository.cs @@ -0,0 +1,8 @@ +using Entities.Models; + +namespace Contracts +{ + public interface IOwnerRepository : IRepositoryBase + { + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryBase.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryBase.cs new file mode 100644 index 0000000000..7a2b84ba33 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryBase.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace Contracts +{ + public interface IRepositoryBase + { + IQueryable FindAll(); + IQueryable FindByCondition(Expression> expression); + void Create(T entity); + void Update(T entity); + void Delete(T entity); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryWrapper.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryWrapper.cs new file mode 100644 index 0000000000..390b08260e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Contracts/IRepositoryWrapper.cs @@ -0,0 +1,9 @@ +namespace Contracts +{ + public interface IRepositoryWrapper + { + IOwnerRepository Owner { get; } + IAccountRepository Account { get; } + Task SaveAsync(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Entities.csproj b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Entities.csproj new file mode 100644 index 0000000000..3d1cecaf08 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Entities.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Account.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Account.cs new file mode 100644 index 0000000000..b2393b53eb --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Account.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Entities.Models +{ + [Table("Account")] + public class Account + { + public Guid AccountId { get; set; } + + [Required(ErrorMessage = "Date created is required")] + public DateTime DateCreated { get; set; } + + [Required(ErrorMessage = "Account type is required")] + public string? AccountType { get; set; } + + [ForeignKey(nameof(Owner))] + public Guid OwnerId { get; set; } + public Owner? Owner { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Owner.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Owner.cs new file mode 100644 index 0000000000..298b8a4b2b --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/Models/Owner.cs @@ -0,0 +1,24 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Entities.Models +{ + [Table("Owner")] + public class Owner + { + public Guid OwnerId { get; set; } + + [Required(ErrorMessage = "Name is required")] + [StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")] + public string? Name { get; set; } + + [Required(ErrorMessage = "Date of birth is required")] + public DateTime DateOfBirth { get; set; } + + [Required(ErrorMessage = "Address is required")] + [StringLength(100, ErrorMessage = "Address cannot be longer than 100 characters")] + public string? Address { get; set; } + + public ICollection? Accounts { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/RepositoryContext.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/RepositoryContext.cs new file mode 100644 index 0000000000..9dfb78e02d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Entities/RepositoryContext.cs @@ -0,0 +1,19 @@ +using Entities.Models; +using Microsoft.EntityFrameworkCore; + +namespace Entities +{ + public class RepositoryContext : DbContext + { + // DbContextOptions, not the non-generic DbContextOptions: + // that is what AddDbContext supplies, and the non-generic form + // fails the moment a second DbContext is registered. + public RepositoryContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Owners => Set(); + public DbSet Accounts => Set(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerManager.cs new file mode 100644 index 0000000000..d776ec8609 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerManager.cs @@ -0,0 +1,18 @@ +using Contracts; +using NLog; + +namespace LoggerService +{ + public class LoggerManager : ILoggerManager + { + private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); + + public void LogDebug(string message) => Logger.Debug(message); + + public void LogError(string message) => Logger.Error(message); + + public void LogInfo(string message) => Logger.Info(message); + + public void LogWarn(string message) => Logger.Warn(message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerService.csproj b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerService.csproj new file mode 100644 index 0000000000..049803efdc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/LoggerService/LoggerService.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/AccountRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/AccountRepository.cs new file mode 100644 index 0000000000..a5efb88935 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/AccountRepository.cs @@ -0,0 +1,14 @@ +using Contracts; +using Entities; +using Entities.Models; + +namespace Repository +{ + public class AccountRepository : RepositoryBase, IAccountRepository + { + public AccountRepository(RepositoryContext repositoryContext) + : base(repositoryContext) + { + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/OwnerRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/OwnerRepository.cs new file mode 100644 index 0000000000..a00c37d3dc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/OwnerRepository.cs @@ -0,0 +1,14 @@ +using Contracts; +using Entities; +using Entities.Models; + +namespace Repository +{ + public class OwnerRepository : RepositoryBase, IOwnerRepository + { + public OwnerRepository(RepositoryContext repositoryContext) + : base(repositoryContext) + { + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/Repository.csproj b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/Repository.csproj new file mode 100644 index 0000000000..a2d04de581 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/Repository.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryBase.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryBase.cs new file mode 100644 index 0000000000..2da56ec386 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryBase.cs @@ -0,0 +1,27 @@ +using Contracts; +using Entities; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; + +namespace Repository +{ + public abstract class RepositoryBase : IRepositoryBase where T : class + { + protected RepositoryContext RepositoryContext { get; set; } + public RepositoryBase(RepositoryContext repositoryContext) + { + RepositoryContext = repositoryContext; + } + + public IQueryable FindAll() => RepositoryContext.Set().AsNoTracking(); + + public IQueryable FindByCondition(Expression> expression) => + RepositoryContext.Set().Where(expression).AsNoTracking(); + + public void Create(T entity) => RepositoryContext.Set().Add(entity); + + public void Update(T entity) => RepositoryContext.Set().Update(entity); + + public void Delete(T entity) => RepositoryContext.Set().Remove(entity); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryWrapper.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryWrapper.cs new file mode 100644 index 0000000000..05b1d936d5 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/Repository/RepositoryWrapper.cs @@ -0,0 +1,18 @@ +using Contracts; +using Entities; + +namespace Repository +{ + public class RepositoryWrapper(RepositoryContext repositoryContext) : IRepositoryWrapper + { + private readonly RepositoryContext _repoContext = repositoryContext; + private IOwnerRepository? _owner; + private IAccountRepository? _account; + + public IOwnerRepository Owner => _owner ??= new OwnerRepository(_repoContext); + + public IAccountRepository Account => _account ??= new AccountRepository(_repoContext); + + public async Task SaveAsync() => await _repoContext.SaveChangesAsync(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer.sln b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer.sln new file mode 100644 index 0000000000..0d08009164 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{0213FB50-5C53-4C34-8B2C-E515C7DC2579}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8E1F895-37AB-455B-8D05-ED495BE48F88} + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/AccountOwnerServer.csproj b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/AccountOwnerServer.csproj new file mode 100644 index 0000000000..a3a34b647c --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/AccountOwnerServer.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Controllers/WeatherForecastController.cs b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000000..bc1f983517 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace AccountOwnerServer.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] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000000..7aa49cc7db --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -0,0 +1,17 @@ +namespace AccountOwnerServer.Extensions +{ + public static class ServiceExtensions + { + public static void ConfigureCors(this IServiceCollection services) + { + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + }); + } + + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Program.cs b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Program.cs new file mode 100644 index 0000000000..bc219f6adc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Program.cs @@ -0,0 +1,32 @@ +using AccountOwnerServer.Extensions; +using Microsoft.AspNetCore.HttpOverrides; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.ConfigureCors(); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +// UseForwardedHeaders goes first: every component after it has to see the client's +// scheme and address, not the proxy's, or an HTTPS redirect behind a proxy loops. +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.All +}); + +if (!app.Environment.IsDevelopment()) + app.UseHsts(); + +app.UseHttpsRedirection(); + +app.UseCors("CorsPolicy"); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Properties/launchSettings.json b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Properties/launchSettings.json new file mode 100644 index 0000000000..c2d94ddfd1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/WeatherForecast.cs b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/WeatherForecast.cs new file mode 100644 index 0000000000..41db3296b6 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace AccountOwnerServer +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.Development.json b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.json b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.json new file mode 100644 index 0000000000..10f68b8c8b --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer.sln b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer.sln new file mode 100644 index 0000000000..78fe059311 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer.sln @@ -0,0 +1,49 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{0213FB50-5C53-4C34-8B2C-E515C7DC2579}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Contracts", "Contracts\Contracts.csproj", "{B731D3C2-9A89-40E4-B96B-AA300B4195BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoggerService", "LoggerService\LoggerService.csproj", "{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Entities\Entities.csproj", "{16AAE663-A20E-493D-9087-F4F4016F6216}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Repository\Repository.csproj", "{A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.Build.0 = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.Build.0 = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.Build.0 = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|Any CPU.Build.0 = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8E1F895-37AB-455B-8D05-ED495BE48F88} + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/AccountOwnerServer.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/AccountOwnerServer.csproj new file mode 100644 index 0000000000..9a34815aae --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/AccountOwnerServer.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Controllers/OwnerController.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Controllers/OwnerController.cs new file mode 100644 index 0000000000..c99f87fdfb --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Controllers/OwnerController.cs @@ -0,0 +1,67 @@ +using AutoMapper; +using Contracts; +using Entities.DataTransferObjects; +using Microsoft.AspNetCore.Mvc; + +namespace AccountOwnerServer.Controllers +{ + [Route("api/owner")] + [ApiController] + public class OwnerController : ControllerBase + { + private readonly ILoggerManager _logger; + private readonly IRepositoryWrapper _repository; + private readonly IMapper _mapper; + + public OwnerController(ILoggerManager logger, IRepositoryWrapper repository, IMapper mapper) + { + _logger = logger; + _repository = repository; + _mapper = mapper; + } + + // No try/catch anywhere in this controller. Exceptions are handled once, by the + // IExceptionHandler registered in Program.cs. + [HttpGet] + public async Task GetAllOwners() + { + var owners = await _repository.Owner.GetAllOwnersAsync(); + _logger.LogInfo("Returned all owners from database."); + + var ownersResult = _mapper.Map>(owners); + return Ok(ownersResult); + } + + [HttpGet("{id}")] + public async Task GetOwnerById(Guid id) + { + var owner = await _repository.Owner.GetOwnerByIdAsync(id); + if (owner is null) + { + _logger.LogError($"Owner with id: {id}, hasn't been found in db."); + return NotFound(); + } + + _logger.LogInfo($"Returned owner with id: {id}"); + + var ownerResult = _mapper.Map(owner); + return Ok(ownerResult); + } + + [HttpGet("{id}/account")] + public async Task GetOwnerWithDetails(Guid id) + { + var owner = await _repository.Owner.GetOwnerWithDetailsAsync(id); + if (owner is null) + { + _logger.LogError($"Owner with id: {id}, hasn't been found in db."); + return NotFound(); + } + + _logger.LogInfo($"Returned owner with details for id: {id}"); + + var ownerResult = _mapper.Map(owner); + return Ok(ownerResult); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs new file mode 100644 index 0000000000..01300562bc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs @@ -0,0 +1,29 @@ +using Contracts; +using Microsoft.AspNetCore.Diagnostics; + +namespace AccountOwnerServer.Errors +{ + // Registered once in Program.cs. Returning true is what stops the exception + // propagating; a handler that returns false falls through to the next one. + public class GlobalExceptionHandler(ILoggerManager logger) : IExceptionHandler + { + private readonly ILoggerManager _logger = logger; + + public async ValueTask TryHandleAsync( + HttpContext httpContext, + Exception exception, + CancellationToken cancellationToken) + { + _logger.LogError($"Something went wrong: {exception.Message}"); + + httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + httpContext.Response.ContentType = "application/json"; + + await httpContext.Response.WriteAsJsonAsync( + new { statusCode = httpContext.Response.StatusCode, message = "Internal Server Error" }, + cancellationToken); + + return true; + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000000..930c501f22 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -0,0 +1,40 @@ +using Contracts; +using LoggerService; +using Microsoft.EntityFrameworkCore; +using Entities; +using Repository; + +namespace AccountOwnerServer.Extensions +{ + public static class ServiceExtensions + { + public static void ConfigureCors(this IServiceCollection services) + { + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + }); + } + + + public static void ConfigureLoggerService(this IServiceCollection services) + { + services.AddSingleton(); + } + + public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration config) + { + var connectionString = config.GetConnectionString("SqlConnection"); + + services.AddDbContext(o => o.UseSqlServer(connectionString)); + } + + public static void ConfigureRepositoryWrapper(this IServiceCollection services) + { + services.AddScoped(); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/MappingProfile.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/MappingProfile.cs new file mode 100644 index 0000000000..41521613a7 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/MappingProfile.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Entities.DataTransferObjects; +using Entities.Models; +using System.Linq; + +namespace AccountOwnerServer +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap(); + + CreateMap(); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Program.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Program.cs new file mode 100644 index 0000000000..eee97e56bb --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Program.cs @@ -0,0 +1,49 @@ +using AccountOwnerServer.Errors; +using AccountOwnerServer.Extensions; +using Microsoft.AspNetCore.HttpOverrides; +using NLog.Web; + +var builder = WebApplication.CreateBuilder(args); + +// NLog is registered as a logging provider, so ILogger reaches the file targets in +// nlog.config as well as ILoggerManager. NLog finds nlog.config in the output directory +// on its own; there is nothing to load by hand. +builder.Logging.ClearProviders(); +builder.Host.UseNLog(); + +builder.Services.ConfigureCors(); +builder.Services.ConfigureLoggerService(); +builder.Services.ConfigureSqlContext(builder.Configuration); +builder.Services.ConfigureRepositoryWrapper(); +builder.Services.AddAutoMapper(typeof(Program)); + +// Exceptions are handled here, once, instead of in a try/catch inside every action. +builder.Services.AddExceptionHandler(); +builder.Services.AddProblemDetails(); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +// UseForwardedHeaders goes first: every component after it has to see the client's +// scheme and address, not the proxy's, or an HTTPS redirect behind a proxy loops. +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.All +}); + +if (!app.Environment.IsDevelopment()) + app.UseHsts(); + +app.UseHttpsRedirection(); + +app.UseExceptionHandler(); + +app.UseCors("CorsPolicy"); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Properties/launchSettings.json b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Properties/launchSettings.json new file mode 100644 index 0000000000..c2d94ddfd1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.Development.json b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.json b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.json new file mode 100644 index 0000000000..939dc1a951 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "SqlConnection": "Server=(localdb)\\MSSQLLocalDB;Database=AccountOwner;Trusted_Connection=True;TrustServerCertificate=True;" + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/nlog.config b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/nlog.config new file mode 100644 index 0000000000..225d1ebc2c --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/nlog.config @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/Contracts.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/Contracts.csproj new file mode 100644 index 0000000000..6633e14a8e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/Contracts.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IAccountRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IAccountRepository.cs new file mode 100644 index 0000000000..825a4fba94 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IAccountRepository.cs @@ -0,0 +1,8 @@ +using Entities.Models; + +namespace Contracts +{ + public interface IAccountRepository + { + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/ILoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/ILoggerManager.cs new file mode 100644 index 0000000000..ee1f08105d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/ILoggerManager.cs @@ -0,0 +1,10 @@ +namespace Contracts +{ + public interface ILoggerManager + { + void LogInfo(string message); + void LogWarn(string message); + void LogDebug(string message); + void LogError(string message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IOwnerRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IOwnerRepository.cs new file mode 100644 index 0000000000..2ca65554b3 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IOwnerRepository.cs @@ -0,0 +1,11 @@ +using Entities.Models; + +namespace Contracts +{ + public interface IOwnerRepository + { + Task> GetAllOwnersAsync(); + Task GetOwnerByIdAsync(Guid ownerId); + Task GetOwnerWithDetailsAsync(Guid ownerId); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryBase.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryBase.cs new file mode 100644 index 0000000000..7a2b84ba33 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryBase.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace Contracts +{ + public interface IRepositoryBase + { + IQueryable FindAll(); + IQueryable FindByCondition(Expression> expression); + void Create(T entity); + void Update(T entity); + void Delete(T entity); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryWrapper.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryWrapper.cs new file mode 100644 index 0000000000..390b08260e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Contracts/IRepositoryWrapper.cs @@ -0,0 +1,9 @@ +namespace Contracts +{ + public interface IRepositoryWrapper + { + IOwnerRepository Owner { get; } + IAccountRepository Account { get; } + Task SaveAsync(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/AccountDto.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/AccountDto.cs new file mode 100644 index 0000000000..7e01c9850d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/AccountDto.cs @@ -0,0 +1,11 @@ +using System; + +namespace Entities.DataTransferObjects +{ + public class AccountDto + { + public Guid Id { get; set; } + public DateTime DateCreated { get; set; } + public string? AccountType { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/OwnerDto.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/OwnerDto.cs new file mode 100644 index 0000000000..1e8fb8e017 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/DataTransferObjects/OwnerDto.cs @@ -0,0 +1,12 @@ +namespace Entities.DataTransferObjects +{ + public class OwnerDto + { + public Guid Id { get; set; } + public string? Name { get; set; } + public DateTime DateOfBirth { get; set; } + public string? Address { get; set; } + + public IEnumerable? Accounts { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Entities.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Entities.csproj new file mode 100644 index 0000000000..3d1cecaf08 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Entities.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Account.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Account.cs new file mode 100644 index 0000000000..e17b19892e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Account.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Entities.Models +{ + [Table("Account")] + public class Account + { + [Column("AccountId")] + public Guid Id { get; set; } + + [Required(ErrorMessage = "Date created is required")] + public DateTime DateCreated { get; set; } + + [Required(ErrorMessage = "Account type is required")] + public string? AccountType { get; set; } + + [ForeignKey(nameof(Owner))] + public Guid OwnerId { get; set; } + public Owner? Owner { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Owner.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Owner.cs new file mode 100644 index 0000000000..1dada77ab1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/Models/Owner.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Entities.Models +{ + [Table("Owner")] + public class Owner + { + [Column("OwnerId")] + public Guid Id { get; set; } + + [Required(ErrorMessage = "Name is required")] + [StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")] + public string? Name { get; set; } + + [Required(ErrorMessage = "Date of birth is required")] + public DateTime DateOfBirth { get; set; } + + [Required(ErrorMessage = "Address is required")] + [StringLength(100, ErrorMessage = "Address cannot be longer than 100 characters")] + public string? Address { get; set; } + + public ICollection? Accounts { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/RepositoryContext.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/RepositoryContext.cs new file mode 100644 index 0000000000..9dfb78e02d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Entities/RepositoryContext.cs @@ -0,0 +1,19 @@ +using Entities.Models; +using Microsoft.EntityFrameworkCore; + +namespace Entities +{ + public class RepositoryContext : DbContext + { + // DbContextOptions, not the non-generic DbContextOptions: + // that is what AddDbContext supplies, and the non-generic form + // fails the moment a second DbContext is registered. + public RepositoryContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Owners => Set(); + public DbSet Accounts => Set(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerManager.cs new file mode 100644 index 0000000000..d776ec8609 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerManager.cs @@ -0,0 +1,18 @@ +using Contracts; +using NLog; + +namespace LoggerService +{ + public class LoggerManager : ILoggerManager + { + private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); + + public void LogDebug(string message) => Logger.Debug(message); + + public void LogError(string message) => Logger.Error(message); + + public void LogInfo(string message) => Logger.Info(message); + + public void LogWarn(string message) => Logger.Warn(message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerService.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerService.csproj new file mode 100644 index 0000000000..049803efdc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/LoggerService/LoggerService.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/AccountRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/AccountRepository.cs new file mode 100644 index 0000000000..a5efb88935 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/AccountRepository.cs @@ -0,0 +1,14 @@ +using Contracts; +using Entities; +using Entities.Models; + +namespace Repository +{ + public class AccountRepository : RepositoryBase, IAccountRepository + { + public AccountRepository(RepositoryContext repositoryContext) + : base(repositoryContext) + { + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/OwnerRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/OwnerRepository.cs new file mode 100644 index 0000000000..54d6b2fe19 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/OwnerRepository.cs @@ -0,0 +1,31 @@ +using Contracts; +using Entities; +using Entities.Models; +using Microsoft.EntityFrameworkCore; + +namespace Repository +{ + public class OwnerRepository : RepositoryBase, IOwnerRepository + { + public OwnerRepository(RepositoryContext repositoryContext) + : base(repositoryContext) + { + } + + // FindAll and FindByCondition stay synchronous: they compose a query, they do + // not execute one. The awaited call is the one that executes. + public async Task> GetAllOwnersAsync() => + await FindAll() + .OrderBy(ow => ow.Name) + .ToListAsync(); + + public async Task GetOwnerByIdAsync(Guid ownerId) => + await FindByCondition(owner => owner.Id.Equals(ownerId)) + .FirstOrDefaultAsync(); + + public async Task GetOwnerWithDetailsAsync(Guid ownerId) => + await FindByCondition(owner => owner.Id.Equals(ownerId)) + .Include(ac => ac.Accounts) + .FirstOrDefaultAsync(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/Repository.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/Repository.csproj new file mode 100644 index 0000000000..a2d04de581 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/Repository.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryBase.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryBase.cs new file mode 100644 index 0000000000..2da56ec386 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryBase.cs @@ -0,0 +1,27 @@ +using Contracts; +using Entities; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; + +namespace Repository +{ + public abstract class RepositoryBase : IRepositoryBase where T : class + { + protected RepositoryContext RepositoryContext { get; set; } + public RepositoryBase(RepositoryContext repositoryContext) + { + RepositoryContext = repositoryContext; + } + + public IQueryable FindAll() => RepositoryContext.Set().AsNoTracking(); + + public IQueryable FindByCondition(Expression> expression) => + RepositoryContext.Set().Where(expression).AsNoTracking(); + + public void Create(T entity) => RepositoryContext.Set().Add(entity); + + public void Update(T entity) => RepositoryContext.Set().Update(entity); + + public void Delete(T entity) => RepositoryContext.Set().Remove(entity); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryWrapper.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryWrapper.cs new file mode 100644 index 0000000000..05b1d936d5 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/Repository/RepositoryWrapper.cs @@ -0,0 +1,18 @@ +using Contracts; +using Entities; + +namespace Repository +{ + public class RepositoryWrapper(RepositoryContext repositoryContext) : IRepositoryWrapper + { + private readonly RepositoryContext _repoContext = repositoryContext; + private IOwnerRepository? _owner; + private IAccountRepository? _account; + + public IOwnerRepository Owner => _owner ??= new OwnerRepository(_repoContext); + + public IAccountRepository Account => _account ??= new AccountRepository(_repoContext); + + public async Task SaveAsync() => await _repoContext.SaveChangesAsync(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/AccountOwnerServer.Tests.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/AccountOwnerServer.Tests.csproj new file mode 100644 index 0000000000..4f4d7c336a --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/AccountOwnerServer.Tests.csproj @@ -0,0 +1,31 @@ + + + + net10.0 + enable + enable + + false + true + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/DockerFactAttribute.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/DockerFactAttribute.cs new file mode 100644 index 0000000000..2e1e07874e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/DockerFactAttribute.cs @@ -0,0 +1,47 @@ +using System.Diagnostics; + +namespace AccountOwnerServer.Tests +{ + // These tests need a SQL Server container. Where Docker is not available they report + // as skipped with a reason, never as passed: a green run that silently tested nothing + // is worse than a red one. + public sealed class DockerFactAttribute : FactAttribute + { + public DockerFactAttribute() + { + if (!DockerEnvironment.IsAvailable.Value) + { + Skip = "Docker is not available here, so the SQL Server container cannot start."; + } + } + } + + internal static class DockerEnvironment + { + public static readonly Lazy IsAvailable = new(Probe); + + private static bool Probe() + { + try + { + using var process = Process.Start(new ProcessStartInfo("docker", "version --format {{.Server.Version}}") + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false + }); + + if (process is null) + { + return false; + } + + return process.WaitForExit(milliseconds: 20_000) && process.ExitCode == 0; + } + catch + { + return false; + } + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/GlobalUsings.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/GlobalUsings.cs new file mode 100644 index 0000000000..c802f4480b --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/OwnerRepositoryTests.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/OwnerRepositoryTests.cs new file mode 100644 index 0000000000..66e35cede5 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/OwnerRepositoryTests.cs @@ -0,0 +1,75 @@ +using Entities.Models; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; + +namespace AccountOwnerServer.Tests +{ + public class OwnerRepositoryTests(SqlServerFixture fixture) : IClassFixture + { + private readonly SqlServerFixture _fixture = fixture; + + [DockerFact] + public async Task WhenGetAllOwnersAsyncIsCalled_ThenItReturnsTheSeededOwnersByName() + { + var repository = _fixture.CreateWrapper(); + + var owners = (await repository.Owner.GetAllOwnersAsync()).ToList(); + + Assert.Equal(4, owners.Count); + Assert.Equal(["Anna Bosh", "John Keen", "Martin Miller", "Sam Query"], owners.Select(o => o.Name)); + } + + [DockerFact] + public async Task WhenGetOwnerWithDetailsAsyncIsCalled_ThenItIncludesTheAccounts() + { + var repository = _fixture.CreateWrapper(); + var johnKeen = Guid.Parse("24fd81f8-d58a-4bcc-9f35-dc6cd5641906"); + + var owner = await repository.Owner.GetOwnerWithDetailsAsync(johnKeen); + + Assert.NotNull(owner); + Assert.Equal(3, owner.Accounts!.Count); + } + + [DockerFact] + public async Task WhenAnOwnerIsCreated_ThenSaveAsyncPersistsIt() + { + var repository = _fixture.CreateWrapper(); + var owner = new Owner + { + Id = Guid.NewGuid(), + Name = "Ada Lovelace", + DateOfBirth = new DateTime(1815, 12, 10), + Address = "12 Analytical Engine Way" + }; + + repository.Owner.CreateOwner(owner); + await repository.SaveAsync(); + + var reloaded = await _fixture.CreateWrapper().Owner.GetOwnerByIdAsync(owner.Id); + Assert.NotNull(reloaded); + Assert.Equal("Ada Lovelace", reloaded.Name); + + // Leave the seeded data as we found it for the other tests. + var cleanup = _fixture.CreateWrapper(); + var toDelete = await cleanup.Owner.GetOwnerByIdAsync(owner.Id); + cleanup.Owner.DeleteOwner(toDelete!); + await cleanup.SaveAsync(); + } + + [DockerFact] + public async Task WhenAnOwnerWithAccountsIsDeleted_ThenTheForeignKeyRejectsIt() + { + var repository = _fixture.CreateWrapper(); + var johnKeen = Guid.Parse("24fd81f8-d58a-4bcc-9f35-dc6cd5641906"); + + var owner = await repository.Owner.GetOwnerByIdAsync(johnKeen); + repository.Owner.DeleteOwner(owner!); + + var exception = await Assert.ThrowsAsync(repository.SaveAsync); + + var sqlException = Assert.IsType(exception.InnerException); + Assert.Equal(547, sqlException.Number); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/SqlServerFixture.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/SqlServerFixture.cs new file mode 100644 index 0000000000..1783eb2969 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.Tests/SqlServerFixture.cs @@ -0,0 +1,57 @@ +using Entities; +using Microsoft.EntityFrameworkCore; +using Repository; +using Testcontainers.MsSql; + +namespace AccountOwnerServer.Tests +{ + // Starts one SQL Server container for the whole test class and creates the schema by + // running the same init.sql part 1 ships. Nothing here calls EnsureCreated: the script + // is the only thing that creates the schema, in the tests as in the article. + public class SqlServerFixture : IAsyncLifetime + { + private MsSqlContainer? _container; + + public string ConnectionString { get; private set; } = string.Empty; + + public async Task InitializeAsync() + { + if (!DockerEnvironment.IsAvailable.Value) + { + return; + } + + _container = new MsSqlBuilder("mcr.microsoft.com/mssql/server:2022-latest").Build(); + await _container.StartAsync(); + + var script = await File.ReadAllTextAsync("init.sql"); + var result = await _container.ExecScriptAsync(script); + if (result.ExitCode != 0) + { + throw new InvalidOperationException($"init.sql failed: {result.Stderr}"); + } + + ConnectionString = _container.GetConnectionString() + .Replace("Database=master", "Database=AccountOwner"); + } + + public async Task DisposeAsync() + { + if (_container is not null) + { + await _container.DisposeAsync(); + } + } + + public RepositoryContext CreateContext() + { + var options = new DbContextOptionsBuilder() + .UseSqlServer(ConnectionString) + .Options; + + return new RepositoryContext(options); + } + + public RepositoryWrapper CreateWrapper() => new(CreateContext()); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.sln b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.sln new file mode 100644 index 0000000000..5940951cc4 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer.sln @@ -0,0 +1,107 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32210.238 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AccountOwnerServer", "AccountOwnerServer\AccountOwnerServer.csproj", "{0213FB50-5C53-4C34-8B2C-E515C7DC2579}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Contracts", "Contracts\Contracts.csproj", "{B731D3C2-9A89-40E4-B96B-AA300B4195BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LoggerService", "LoggerService\LoggerService.csproj", "{7CEC76F5-80AF-4B37-B6F1-94A743E5C748}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Entities", "Entities\Entities.csproj", "{16AAE663-A20E-493D-9087-F4F4016F6216}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Repository", "Repository\Repository.csproj", "{A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccountOwnerServer.Tests", "AccountOwnerServer.Tests\AccountOwnerServer.Tests.csproj", "{EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|x64.ActiveCfg = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|x64.Build.0 = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|x86.ActiveCfg = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Debug|x86.Build.0 = Debug|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|Any CPU.Build.0 = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|x64.ActiveCfg = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|x64.Build.0 = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|x86.ActiveCfg = Release|Any CPU + {0213FB50-5C53-4C34-8B2C-E515C7DC2579}.Release|x86.Build.0 = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|x64.ActiveCfg = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|x64.Build.0 = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|x86.ActiveCfg = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Debug|x86.Build.0 = Debug|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|Any CPU.Build.0 = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|x64.ActiveCfg = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|x64.Build.0 = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|x86.ActiveCfg = Release|Any CPU + {B731D3C2-9A89-40E4-B96B-AA300B4195BB}.Release|x86.Build.0 = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|x64.Build.0 = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|x86.ActiveCfg = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Debug|x86.Build.0 = Debug|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|Any CPU.Build.0 = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|x64.ActiveCfg = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|x64.Build.0 = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|x86.ActiveCfg = Release|Any CPU + {7CEC76F5-80AF-4B37-B6F1-94A743E5C748}.Release|x86.Build.0 = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|Any CPU.Build.0 = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|x64.ActiveCfg = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|x64.Build.0 = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|x86.ActiveCfg = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Debug|x86.Build.0 = Debug|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|Any CPU.ActiveCfg = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|Any CPU.Build.0 = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|x64.ActiveCfg = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|x64.Build.0 = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|x86.ActiveCfg = Release|Any CPU + {16AAE663-A20E-493D-9087-F4F4016F6216}.Release|x86.Build.0 = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|x64.ActiveCfg = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|x64.Build.0 = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|x86.ActiveCfg = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Debug|x86.Build.0 = Debug|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|Any CPU.Build.0 = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|x64.ActiveCfg = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|x64.Build.0 = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|x86.ActiveCfg = Release|Any CPU + {A0C0D726-5224-4B8F-8C05-BB2C12A78D9B}.Release|x86.Build.0 = Release|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Debug|x64.ActiveCfg = Debug|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Debug|x64.Build.0 = Debug|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Debug|x86.ActiveCfg = Debug|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Debug|x86.Build.0 = Debug|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Release|Any CPU.Build.0 = Release|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Release|x64.ActiveCfg = Release|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Release|x64.Build.0 = Release|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Release|x86.ActiveCfg = Release|Any CPU + {EEE4FB17-4B8F-45A0-9FC0-0AAA91E12EA9}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F8E1F895-37AB-455B-8D05-ED495BE48F88} + EndGlobalSection +EndGlobal diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/AccountOwnerServer.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/AccountOwnerServer.csproj new file mode 100644 index 0000000000..9a34815aae --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/AccountOwnerServer.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Controllers/OwnerController.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Controllers/OwnerController.cs new file mode 100644 index 0000000000..00d68a9629 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Controllers/OwnerController.cs @@ -0,0 +1,130 @@ +using AutoMapper; +using Contracts; +using Entities.DataTransferObjects; +using Entities.Models; +using Microsoft.AspNetCore.Mvc; + +namespace AccountOwnerServer.Controllers +{ + [Route("api/owner")] + [ApiController] + public class OwnerController : ControllerBase + { + private readonly ILoggerManager _logger; + private readonly IRepositoryWrapper _repository; + private readonly IMapper _mapper; + + public OwnerController(ILoggerManager logger, IRepositoryWrapper repository, IMapper mapper) + { + _logger = logger; + _repository = repository; + _mapper = mapper; + } + + // No try/catch and no model-state guards. [ApiController] rejects an empty or + // invalid body with a 400 and a ValidationProblemDetails before the action runs, + // and everything else is handled by the IExceptionHandler in Program.cs. + [HttpGet] + public async Task GetAllOwners() + { + var owners = await _repository.Owner.GetAllOwnersAsync(); + _logger.LogInfo("Returned all owners from database."); + + var ownersResult = _mapper.Map>(owners); + return Ok(ownersResult); + } + + // The route is named because CreatedAtRoute looks it up by name to build the + // Location header. The name is OwnerById and it does not change with the method. + [HttpGet("{id}", Name = "OwnerById")] + public async Task GetOwnerById(Guid id) + { + var owner = await _repository.Owner.GetOwnerByIdAsync(id); + if (owner is null) + { + _logger.LogError($"Owner with id: {id}, hasn't been found in db."); + return NotFound(); + } + + _logger.LogInfo($"Returned owner with id: {id}"); + + var ownerResult = _mapper.Map(owner); + return Ok(ownerResult); + } + + [HttpGet("{id}/account")] + public async Task GetOwnerWithDetails(Guid id) + { + var owner = await _repository.Owner.GetOwnerWithDetailsAsync(id); + if (owner is null) + { + _logger.LogError($"Owner with id: {id}, hasn't been found in db."); + return NotFound(); + } + + _logger.LogInfo($"Returned owner with details for id: {id}"); + + var ownerResult = _mapper.Map(owner); + return Ok(ownerResult); + } + + [HttpPost] + public async Task CreateOwner([FromBody] OwnerForCreationDto owner) + { + var ownerEntity = _mapper.Map(owner); + + _repository.Owner.CreateOwner(ownerEntity); + await _repository.SaveAsync(); + + var createdOwner = _mapper.Map(ownerEntity); + + return CreatedAtRoute("OwnerById", new { id = createdOwner.Id }, createdOwner); + } + + [HttpPut("{id}")] + public async Task UpdateOwner(Guid id, [FromBody] OwnerForUpdateDto owner) + { + var ownerEntity = await _repository.Owner.GetOwnerByIdAsync(id); + if (ownerEntity is null) + { + _logger.LogError($"Owner with id: {id}, hasn't been found in db."); + return NotFound(); + } + + // Map onto the entity we just read: properties the DTO does not carry, the + // id and the accounts among them, keep the values they already have. + _mapper.Map(owner, ownerEntity); + + _repository.Owner.UpdateOwner(ownerEntity); + await _repository.SaveAsync(); + + return NoContent(); + } + + [HttpDelete("{id}")] + public async Task DeleteOwner(Guid id) + { + var owner = await _repository.Owner.GetOwnerByIdAsync(id); + if (owner is null) + { + _logger.LogError($"Owner with id: {id}, hasn't been found in db."); + return NotFound(); + } + + // The pre-check buys a readable error message. The foreign key is the real + // guard: an account inserted between this check and the save makes the + // delete fail with a DbUpdateException wrapping SqlException 547. + var accounts = await _repository.Account.AccountsByOwnerAsync(id); + if (accounts.Any()) + { + _logger.LogError($"Cannot delete owner with id: {id}. It has related accounts."); + return BadRequest("Cannot delete owner. It has related accounts. Delete those accounts first"); + } + + _repository.Owner.DeleteOwner(owner); + await _repository.SaveAsync(); + + return NoContent(); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs new file mode 100644 index 0000000000..01300562bc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Errors/GlobalExceptionHandler.cs @@ -0,0 +1,29 @@ +using Contracts; +using Microsoft.AspNetCore.Diagnostics; + +namespace AccountOwnerServer.Errors +{ + // Registered once in Program.cs. Returning true is what stops the exception + // propagating; a handler that returns false falls through to the next one. + public class GlobalExceptionHandler(ILoggerManager logger) : IExceptionHandler + { + private readonly ILoggerManager _logger = logger; + + public async ValueTask TryHandleAsync( + HttpContext httpContext, + Exception exception, + CancellationToken cancellationToken) + { + _logger.LogError($"Something went wrong: {exception.Message}"); + + httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError; + httpContext.Response.ContentType = "application/json"; + + await httpContext.Response.WriteAsJsonAsync( + new { statusCode = httpContext.Response.StatusCode, message = "Internal Server Error" }, + cancellationToken); + + return true; + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs new file mode 100644 index 0000000000..930c501f22 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -0,0 +1,40 @@ +using Contracts; +using LoggerService; +using Microsoft.EntityFrameworkCore; +using Entities; +using Repository; + +namespace AccountOwnerServer.Extensions +{ + public static class ServiceExtensions + { + public static void ConfigureCors(this IServiceCollection services) + { + services.AddCors(options => + { + options.AddPolicy("CorsPolicy", + builder => builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader()); + }); + } + + + public static void ConfigureLoggerService(this IServiceCollection services) + { + services.AddSingleton(); + } + + public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration config) + { + var connectionString = config.GetConnectionString("SqlConnection"); + + services.AddDbContext(o => o.UseSqlServer(connectionString)); + } + + public static void ConfigureRepositoryWrapper(this IServiceCollection services) + { + services.AddScoped(); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/MappingProfile.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/MappingProfile.cs new file mode 100644 index 0000000000..7443348630 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/MappingProfile.cs @@ -0,0 +1,21 @@ +using AutoMapper; +using Entities.DataTransferObjects; +using Entities.Models; +using System.Linq; + +namespace AccountOwnerServer +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap(); + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Program.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Program.cs new file mode 100644 index 0000000000..eee97e56bb --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Program.cs @@ -0,0 +1,49 @@ +using AccountOwnerServer.Errors; +using AccountOwnerServer.Extensions; +using Microsoft.AspNetCore.HttpOverrides; +using NLog.Web; + +var builder = WebApplication.CreateBuilder(args); + +// NLog is registered as a logging provider, so ILogger reaches the file targets in +// nlog.config as well as ILoggerManager. NLog finds nlog.config in the output directory +// on its own; there is nothing to load by hand. +builder.Logging.ClearProviders(); +builder.Host.UseNLog(); + +builder.Services.ConfigureCors(); +builder.Services.ConfigureLoggerService(); +builder.Services.ConfigureSqlContext(builder.Configuration); +builder.Services.ConfigureRepositoryWrapper(); +builder.Services.AddAutoMapper(typeof(Program)); + +// Exceptions are handled here, once, instead of in a try/catch inside every action. +builder.Services.AddExceptionHandler(); +builder.Services.AddProblemDetails(); + +builder.Services.AddControllers(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +// UseForwardedHeaders goes first: every component after it has to see the client's +// scheme and address, not the proxy's, or an HTTPS redirect behind a proxy loops. +app.UseForwardedHeaders(new ForwardedHeadersOptions +{ + ForwardedHeaders = ForwardedHeaders.All +}); + +if (!app.Environment.IsDevelopment()) + app.UseHsts(); + +app.UseHttpsRedirection(); + +app.UseExceptionHandler(); + +app.UseCors("CorsPolicy"); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Properties/launchSettings.json b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Properties/launchSettings.json new file mode 100644 index 0000000000..c2d94ddfd1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.Development.json b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.json b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.json new file mode 100644 index 0000000000..939dc1a951 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "SqlConnection": "Server=(localdb)\\MSSQLLocalDB;Database=AccountOwner;Trusted_Connection=True;TrustServerCertificate=True;" + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/nlog.config b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/nlog.config new file mode 100644 index 0000000000..225d1ebc2c --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/nlog.config @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/Contracts.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/Contracts.csproj new file mode 100644 index 0000000000..6633e14a8e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/Contracts.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IAccountRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IAccountRepository.cs new file mode 100644 index 0000000000..86473573cd --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IAccountRepository.cs @@ -0,0 +1,9 @@ +using Entities.Models; + +namespace Contracts +{ + public interface IAccountRepository + { + Task> AccountsByOwnerAsync(Guid ownerId); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/ILoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/ILoggerManager.cs new file mode 100644 index 0000000000..ee1f08105d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/ILoggerManager.cs @@ -0,0 +1,10 @@ +namespace Contracts +{ + public interface ILoggerManager + { + void LogInfo(string message); + void LogWarn(string message); + void LogDebug(string message); + void LogError(string message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IOwnerRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IOwnerRepository.cs new file mode 100644 index 0000000000..5ba495962e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IOwnerRepository.cs @@ -0,0 +1,14 @@ +using Entities.Models; + +namespace Contracts +{ + public interface IOwnerRepository + { + Task> GetAllOwnersAsync(); + Task GetOwnerByIdAsync(Guid ownerId); + Task GetOwnerWithDetailsAsync(Guid ownerId); + void CreateOwner(Owner owner); + void UpdateOwner(Owner owner); + void DeleteOwner(Owner owner); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryBase.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryBase.cs new file mode 100644 index 0000000000..7a2b84ba33 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryBase.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text; + +namespace Contracts +{ + public interface IRepositoryBase + { + IQueryable FindAll(); + IQueryable FindByCondition(Expression> expression); + void Create(T entity); + void Update(T entity); + void Delete(T entity); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryWrapper.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryWrapper.cs new file mode 100644 index 0000000000..390b08260e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Contracts/IRepositoryWrapper.cs @@ -0,0 +1,9 @@ +namespace Contracts +{ + public interface IRepositoryWrapper + { + IOwnerRepository Owner { get; } + IAccountRepository Account { get; } + Task SaveAsync(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/AccountDto.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/AccountDto.cs new file mode 100644 index 0000000000..7e01c9850d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/AccountDto.cs @@ -0,0 +1,11 @@ +using System; + +namespace Entities.DataTransferObjects +{ + public class AccountDto + { + public Guid Id { get; set; } + public DateTime DateCreated { get; set; } + public string? AccountType { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerDto.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerDto.cs new file mode 100644 index 0000000000..1e8fb8e017 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerDto.cs @@ -0,0 +1,12 @@ +namespace Entities.DataTransferObjects +{ + public class OwnerDto + { + public Guid Id { get; set; } + public string? Name { get; set; } + public DateTime DateOfBirth { get; set; } + public string? Address { get; set; } + + public IEnumerable? Accounts { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForCreationDto.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForCreationDto.cs new file mode 100644 index 0000000000..78ce531436 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForCreationDto.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Entities.DataTransferObjects +{ + public class OwnerForCreationDto + { + [Required(ErrorMessage = "Name is required")] + [StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")] + public string? Name { get; set; } + + [Required(ErrorMessage = "Date of birth is required")] + public DateTime DateOfBirth { get; set; } + + [Required(ErrorMessage = "Address is required")] + [StringLength(100, ErrorMessage = "Address cannot be longer than 100 characters")] + public string? Address { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForUpdateDto.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForUpdateDto.cs new file mode 100644 index 0000000000..d22337d474 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/DataTransferObjects/OwnerForUpdateDto.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace Entities.DataTransferObjects +{ + public class OwnerForUpdateDto + { + [Required(ErrorMessage = "Name is required")] + [StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")] + public string? Name { get; set; } + + [Required(ErrorMessage = "Date of birth is required")] + public DateTime DateOfBirth { get; set; } + + [Required(ErrorMessage = "Address is required")] + [StringLength(100, ErrorMessage = "Address cannot be longer than 100 characters")] + public string? Address { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Entities.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Entities.csproj new file mode 100644 index 0000000000..3d1cecaf08 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Entities.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Account.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Account.cs new file mode 100644 index 0000000000..e17b19892e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Account.cs @@ -0,0 +1,22 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Entities.Models +{ + [Table("Account")] + public class Account + { + [Column("AccountId")] + public Guid Id { get; set; } + + [Required(ErrorMessage = "Date created is required")] + public DateTime DateCreated { get; set; } + + [Required(ErrorMessage = "Account type is required")] + public string? AccountType { get; set; } + + [ForeignKey(nameof(Owner))] + public Guid OwnerId { get; set; } + public Owner? Owner { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Owner.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Owner.cs new file mode 100644 index 0000000000..1dada77ab1 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/Models/Owner.cs @@ -0,0 +1,25 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Entities.Models +{ + [Table("Owner")] + public class Owner + { + [Column("OwnerId")] + public Guid Id { get; set; } + + [Required(ErrorMessage = "Name is required")] + [StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")] + public string? Name { get; set; } + + [Required(ErrorMessage = "Date of birth is required")] + public DateTime DateOfBirth { get; set; } + + [Required(ErrorMessage = "Address is required")] + [StringLength(100, ErrorMessage = "Address cannot be longer than 100 characters")] + public string? Address { get; set; } + + public ICollection? Accounts { get; set; } + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/RepositoryContext.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/RepositoryContext.cs new file mode 100644 index 0000000000..9dfb78e02d --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Entities/RepositoryContext.cs @@ -0,0 +1,19 @@ +using Entities.Models; +using Microsoft.EntityFrameworkCore; + +namespace Entities +{ + public class RepositoryContext : DbContext + { + // DbContextOptions, not the non-generic DbContextOptions: + // that is what AddDbContext supplies, and the non-generic form + // fails the moment a second DbContext is registered. + public RepositoryContext(DbContextOptions options) + : base(options) + { + } + + public DbSet Owners => Set(); + public DbSet Accounts => Set(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerManager.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerManager.cs new file mode 100644 index 0000000000..d776ec8609 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerManager.cs @@ -0,0 +1,18 @@ +using Contracts; +using NLog; + +namespace LoggerService +{ + public class LoggerManager : ILoggerManager + { + private static readonly ILogger Logger = LogManager.GetCurrentClassLogger(); + + public void LogDebug(string message) => Logger.Debug(message); + + public void LogError(string message) => Logger.Error(message); + + public void LogInfo(string message) => Logger.Info(message); + + public void LogWarn(string message) => Logger.Warn(message); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerService.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerService.csproj new file mode 100644 index 0000000000..049803efdc --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/LoggerService/LoggerService.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + + + + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/AccountRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/AccountRepository.cs new file mode 100644 index 0000000000..30bff45325 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/AccountRepository.cs @@ -0,0 +1,18 @@ +using Contracts; +using Entities; +using Entities.Models; +using Microsoft.EntityFrameworkCore; + +namespace Repository +{ + public class AccountRepository : RepositoryBase, IAccountRepository + { + public AccountRepository(RepositoryContext repositoryContext) + : base(repositoryContext) + { + } + + public async Task> AccountsByOwnerAsync(Guid ownerId) => + await FindByCondition(a => a.OwnerId.Equals(ownerId)).ToListAsync(); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/OwnerRepository.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/OwnerRepository.cs new file mode 100644 index 0000000000..ba58115891 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/OwnerRepository.cs @@ -0,0 +1,39 @@ +using Contracts; +using Entities; +using Entities.Models; +using Microsoft.EntityFrameworkCore; + +namespace Repository +{ + public class OwnerRepository : RepositoryBase, IOwnerRepository + { + public OwnerRepository(RepositoryContext repositoryContext) + : base(repositoryContext) + { + } + + // FindAll and FindByCondition stay synchronous: they compose a query, they do + // not execute one. The awaited call is the one that executes. + public async Task> GetAllOwnersAsync() => + await FindAll() + .OrderBy(ow => ow.Name) + .ToListAsync(); + + public async Task GetOwnerByIdAsync(Guid ownerId) => + await FindByCondition(owner => owner.Id.Equals(ownerId)) + .FirstOrDefaultAsync(); + + public async Task GetOwnerWithDetailsAsync(Guid ownerId) => + await FindByCondition(owner => owner.Id.Equals(ownerId)) + .Include(ac => ac.Accounts) + .FirstOrDefaultAsync(); + + // Create, Update and Delete only stage a change on the change tracker. There is + // nothing to await here; the awaited call is SaveAsync on the wrapper. + public void CreateOwner(Owner owner) => Create(owner); + + public void UpdateOwner(Owner owner) => Update(owner); + + public void DeleteOwner(Owner owner) => Delete(owner); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/Repository.csproj b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/Repository.csproj new file mode 100644 index 0000000000..a2d04de581 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/Repository.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + + + + + + + + diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryBase.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryBase.cs new file mode 100644 index 0000000000..2da56ec386 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryBase.cs @@ -0,0 +1,27 @@ +using Contracts; +using Entities; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; + +namespace Repository +{ + public abstract class RepositoryBase : IRepositoryBase where T : class + { + protected RepositoryContext RepositoryContext { get; set; } + public RepositoryBase(RepositoryContext repositoryContext) + { + RepositoryContext = repositoryContext; + } + + public IQueryable FindAll() => RepositoryContext.Set().AsNoTracking(); + + public IQueryable FindByCondition(Expression> expression) => + RepositoryContext.Set().Where(expression).AsNoTracking(); + + public void Create(T entity) => RepositoryContext.Set().Add(entity); + + public void Update(T entity) => RepositoryContext.Set().Update(entity); + + public void Delete(T entity) => RepositoryContext.Set().Remove(entity); + } +} diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryWrapper.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryWrapper.cs new file mode 100644 index 0000000000..05b1d936d5 --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/Repository/RepositoryWrapper.cs @@ -0,0 +1,18 @@ +using Contracts; +using Entities; + +namespace Repository +{ + public class RepositoryWrapper(RepositoryContext repositoryContext) : IRepositoryWrapper + { + private readonly RepositoryContext _repoContext = repositoryContext; + private IOwnerRepository? _owner; + private IAccountRepository? _account; + + public IOwnerRepository Owner => _owner ??= new OwnerRepository(_repoContext); + + public IAccountRepository Account => _account ??= new AccountRepository(_repoContext); + + public async Task SaveAsync() => await _repoContext.SaveChangesAsync(); + } +} From b3aac305a4e8d21f22ebb143a7fa2d2d3bebc6a5 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 30 Aug 2026 16:11:54 +0200 Subject: [PATCH 2/3] Basic Web API series: tidy ServiceExtensions whitespace left by the IIS removal --- .../Extensions/ServiceExtensions.cs | 7 +++---- .../Extensions/ServiceExtensions.cs | 11 +++++------ .../Extensions/ServiceExtensions.cs | 3 +-- .../Extensions/ServiceExtensions.cs | 11 +++++------ .../Extensions/ServiceExtensions.cs | 11 +++++------ 5 files changed, 19 insertions(+), 24 deletions(-) diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs index 061ab690cd..6662b38c9d 100644 --- a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -1,4 +1,4 @@ -using Contracts; +using Contracts; using LoggerService; namespace AccountOwnerServer.Extensions @@ -16,10 +16,9 @@ public static void ConfigureCors(this IServiceCollection services) }); } - - public static void ConfigureLoggerService(this IServiceCollection services) + public static void ConfigureLoggerService(this IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); } } } diff --git a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs index 930c501f22..4933589f57 100644 --- a/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs +++ b/aspnetcore-webapi/BasicWebApiSeries/RepositoryPatternWithEfCore/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -19,10 +19,9 @@ public static void ConfigureCors(this IServiceCollection services) }); } - - public static void ConfigureLoggerService(this IServiceCollection services) + public static void ConfigureLoggerService(this IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); } public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration config) @@ -32,9 +31,9 @@ public static void ConfigureSqlContext(this IServiceCollection services, IConfig services.AddDbContext(o => o.UseSqlServer(connectionString)); } - public static void ConfigureRepositoryWrapper(this IServiceCollection services) - { - services.AddScoped(); + public static void ConfigureRepositoryWrapper(this IServiceCollection services) + { + services.AddScoped(); } } } diff --git a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs index 7aa49cc7db..4413bd1bb6 100644 --- a/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs +++ b/aspnetcore-webapi/BasicWebApiSeries/ServiceConfiguration/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -1,4 +1,4 @@ -namespace AccountOwnerServer.Extensions +namespace AccountOwnerServer.Extensions { public static class ServiceExtensions { @@ -12,6 +12,5 @@ public static void ConfigureCors(this IServiceCollection services) .AllowAnyHeader()); }); } - } } diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs index 930c501f22..4933589f57 100644 --- a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForGetRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -19,10 +19,9 @@ public static void ConfigureCors(this IServiceCollection services) }); } - - public static void ConfigureLoggerService(this IServiceCollection services) + public static void ConfigureLoggerService(this IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); } public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration config) @@ -32,9 +31,9 @@ public static void ConfigureSqlContext(this IServiceCollection services, IConfig services.AddDbContext(o => o.UseSqlServer(connectionString)); } - public static void ConfigureRepositoryWrapper(this IServiceCollection services) - { - services.AddScoped(); + public static void ConfigureRepositoryWrapper(this IServiceCollection services) + { + services.AddScoped(); } } } diff --git a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs index 930c501f22..4933589f57 100644 --- a/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs +++ b/aspnetcore-webapi/BasicWebApiSeries/UsingRepositoryForWriteRequests/AccountOwnerServer/Extensions/ServiceExtensions.cs @@ -19,10 +19,9 @@ public static void ConfigureCors(this IServiceCollection services) }); } - - public static void ConfigureLoggerService(this IServiceCollection services) + public static void ConfigureLoggerService(this IServiceCollection services) { - services.AddSingleton(); + services.AddSingleton(); } public static void ConfigureSqlContext(this IServiceCollection services, IConfiguration config) @@ -32,9 +31,9 @@ public static void ConfigureSqlContext(this IServiceCollection services, IConfig services.AddDbContext(o => o.UseSqlServer(connectionString)); } - public static void ConfigureRepositoryWrapper(this IServiceCollection services) - { - services.AddScoped(); + public static void ConfigureRepositoryWrapper(this IServiceCollection services) + { + services.AddScoped(); } } } From 7901d30df904a9400f431f3e79009e31f0e471d0 Mon Sep 17 00:00:00 2001 From: Vladimir Pecanac Date: Sun, 30 Aug 2026 16:42:50 +0200 Subject: [PATCH 3/3] Basic Web API series: add the ILogger controller the logging part prints --- .../WeatherForecastWithILoggerController.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastWithILoggerController.cs diff --git a/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastWithILoggerController.cs b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastWithILoggerController.cs new file mode 100644 index 0000000000..b59ea48e9e --- /dev/null +++ b/aspnetcore-webapi/BasicWebApiSeries/Logging/AccountOwnerServer/Controllers/WeatherForecastWithILoggerController.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Mvc; + +namespace AccountOwnerServer.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastWithILoggerController : ControllerBase + { + private readonly ILogger _logger; + + public WeatherForecastWithILoggerController(ILogger logger) + { + _logger = logger; + } + + // The platform way: no registration, and the message is a template with a named + // placeholder. WeatherForecastController takes the ILoggerManager wrapper instead, + // and both reach the same file because NLog is registered as a provider. + [HttpGet] + public IEnumerable Get() + { + _logger.LogInformation("Serving {Count} forecast values", 2); + + return ["value1", "value2"]; + } + } +}