From f78ab09dc01e1d754b95a612e20af1d8107047b4 Mon Sep 17 00:00:00 2001 From: Evgenii Egorov Date: Thu, 27 Apr 2023 18:15:16 +0300 Subject: [PATCH 1/5] Implemented Swagger and SwaggerUI. --- Properties/launchSettings.json | 1 + Startup.cs | 3 +++ TodoApiDTO.csproj | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 6766196a..99d62fe6 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -18,6 +18,7 @@ "TodoApiDTO": { "commandName": "Project", "launchBrowser": true, + "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, diff --git a/Startup.cs b/Startup.cs index bbfbc83d..66f0ccc9 100644 --- a/Startup.cs +++ b/Startup.cs @@ -30,6 +30,7 @@ public void ConfigureServices(IServiceCollection services) services.AddDbContext(opt => opt.UseInMemoryDatabase("TodoList")); services.AddControllers(); + services.AddSwaggerGen(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -39,6 +40,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); } + app.UseSwagger(); + app.UseSwaggerUI(); app.UseHttpsRedirection(); diff --git a/TodoApiDTO.csproj b/TodoApiDTO.csproj index bba6f6af..f9ed1f08 100644 --- a/TodoApiDTO.csproj +++ b/TodoApiDTO.csproj @@ -12,6 +12,14 @@ + + + + + + + PreserveNewest + From 4763e93a474f2724a5e8dd7b5fc57670addd14ce Mon Sep 17 00:00:00 2001 From: Evgenii Egorov Date: Thu, 27 Apr 2023 19:01:21 +0300 Subject: [PATCH 2/5] - Implemented a DataAccessLayer to improve the separation of concerns; - Changed project's default namespace to TodoApi. --- Controllers/TodoItemsController.cs | 2 + DataAccessLayer/Abstract/IGenericDal.cs | 15 +++++ DataAccessLayer/Abstract/ITodoItemDal.cs | 8 +++ .../Context}/TodoContext.cs | 3 +- DataAccessLayer/DependencyInjection.cs | 15 +++++ .../EntityFramework/EFTodoItemRepository.cs | 14 ++++ .../Repositories/GenericRepository.cs | 67 +++++++++++++++++++ EntityLayer/Entities/Abstract/BaseEntity.cs | 11 +++ {Models => EntityLayer/Entities}/TodoItem.cs | 7 +- Startup.cs | 11 +-- TodoApiDTO.csproj | 5 ++ 11 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 DataAccessLayer/Abstract/IGenericDal.cs create mode 100644 DataAccessLayer/Abstract/ITodoItemDal.cs rename {Models => DataAccessLayer/Context}/TodoContext.cs (77%) create mode 100644 DataAccessLayer/DependencyInjection.cs create mode 100644 DataAccessLayer/EntityFramework/EFTodoItemRepository.cs create mode 100644 DataAccessLayer/Repositories/GenericRepository.cs create mode 100644 EntityLayer/Entities/Abstract/BaseEntity.cs rename {Models => EntityLayer/Entities}/TodoItem.cs (58%) diff --git a/Controllers/TodoItemsController.cs b/Controllers/TodoItemsController.cs index 0ef138e7..c2cca5c3 100644 --- a/Controllers/TodoItemsController.cs +++ b/Controllers/TodoItemsController.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using TodoApi.DataAccessLayer.Context; +using TodoApi.EntityLayer.Entities; using TodoApi.Models; namespace TodoApi.Controllers diff --git a/DataAccessLayer/Abstract/IGenericDal.cs b/DataAccessLayer/Abstract/IGenericDal.cs new file mode 100644 index 00000000..0654cc1c --- /dev/null +++ b/DataAccessLayer/Abstract/IGenericDal.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoApi.EntityLayer.Entities.Abstract; + +namespace TodoApi.DataAccessLayer.Abstract +{ + public interface IGenericDal where T: BaseEntity + { + public Task CreateAsync(T item); + public Task GetAsync(long id); + public Task> GetAllAsync(); + public Task UpdateAsync(long id, T item); + public Task DeleteAsync(long id); + } +} diff --git a/DataAccessLayer/Abstract/ITodoItemDal.cs b/DataAccessLayer/Abstract/ITodoItemDal.cs new file mode 100644 index 00000000..ef03cfe3 --- /dev/null +++ b/DataAccessLayer/Abstract/ITodoItemDal.cs @@ -0,0 +1,8 @@ +using TodoApi.EntityLayer.Entities; + +namespace TodoApi.DataAccessLayer.Abstract +{ + public interface ITodoItemDal : IGenericDal + { + } +} diff --git a/Models/TodoContext.cs b/DataAccessLayer/Context/TodoContext.cs similarity index 77% rename from Models/TodoContext.cs rename to DataAccessLayer/Context/TodoContext.cs index 6e59e363..c7903133 100644 --- a/Models/TodoContext.cs +++ b/DataAccessLayer/Context/TodoContext.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; +using TodoApi.EntityLayer.Entities; -namespace TodoApi.Models +namespace TodoApi.DataAccessLayer.Context { public class TodoContext : DbContext { diff --git a/DataAccessLayer/DependencyInjection.cs b/DataAccessLayer/DependencyInjection.cs new file mode 100644 index 00000000..fe980dd1 --- /dev/null +++ b/DataAccessLayer/DependencyInjection.cs @@ -0,0 +1,15 @@ +using Microsoft.Extensions.DependencyInjection; +using TodoApi.DataAccessLayer.Abstract; +using TodoApi.DataAccessLayer.EntityFramework; + +namespace TodoApi.DataAccessLayer +{ + public static class DependencyInjection + { + public static IServiceCollection AddDataRepositories(this IServiceCollection services) + { + services.AddScoped(); + return services; + } + } +} diff --git a/DataAccessLayer/EntityFramework/EFTodoItemRepository.cs b/DataAccessLayer/EntityFramework/EFTodoItemRepository.cs new file mode 100644 index 00000000..0d8e06a0 --- /dev/null +++ b/DataAccessLayer/EntityFramework/EFTodoItemRepository.cs @@ -0,0 +1,14 @@ +using TodoApi.DataAccessLayer.Abstract; +using TodoApi.DataAccessLayer.Context; +using TodoApi.DataAccessLayer.Repositories; +using TodoApi.EntityLayer.Entities; + +namespace TodoApi.DataAccessLayer.EntityFramework +{ + public class EFTodoItemRepository : GenericRepository, ITodoItemDal + { + public EFTodoItemRepository(TodoContext context) : base(context) + { + } + } +} diff --git a/DataAccessLayer/Repositories/GenericRepository.cs b/DataAccessLayer/Repositories/GenericRepository.cs new file mode 100644 index 00000000..f9242739 --- /dev/null +++ b/DataAccessLayer/Repositories/GenericRepository.cs @@ -0,0 +1,67 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TodoApi.DataAccessLayer.Abstract; +using TodoApi.DataAccessLayer.Context; +using TodoApi.EntityLayer.Entities.Abstract; + +namespace TodoApi.DataAccessLayer.Repositories +{ + public class GenericRepository : IGenericDal where T : BaseEntity + { + protected private TodoContext _context; + + public GenericRepository(TodoContext context) + { + _context = context; + } + + public async Task CreateAsync(T item) + { + _context.Add(item); + await _context.SaveChangesAsync(); + return item; + } + public async Task DeleteAsync(long id) + { + var item = await _context.Set().FindAsync(id) + ?? throw new NullReferenceException("The item with the provided 'id' doesn't exist in the db."); + _context.Remove(item); + await _context.SaveChangesAsync(); + } + + public async Task> GetAllAsync() + { + return await _context.Set().ToListAsync(); + } + public async Task GetAsync(long id) + { + return await _context.Set().FindAsync(id) ?? throw new NullReferenceException("The item with the provided 'id' doesn't exist in the db."); + } + public async Task UpdateAsync(long id, T item) + { + if (id != item.Id) + { + throw new ArgumentException("The 'id' parameter doesn't match the 'Id' property of the 'item'."); + } + if (!ItemExists(_context, id)) + throw new NullReferenceException("The item with the provided 'id' doesn't exist in the db."); + _context.Update(item); + try + { + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) when (!ItemExists(_context, id)) + { + throw new InvalidOperationException("An error occurred while attempting to modify the item with the 'id' in the db. " + + "The item no longer exists in the database."); + } + } + + private static bool ItemExists(DbContext context, long id) => + context.Set().Any(e => e.Id == id); + } +} diff --git a/EntityLayer/Entities/Abstract/BaseEntity.cs b/EntityLayer/Entities/Abstract/BaseEntity.cs new file mode 100644 index 00000000..510c799a --- /dev/null +++ b/EntityLayer/Entities/Abstract/BaseEntity.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace TodoApi.EntityLayer.Entities.Abstract +{ + public abstract class BaseEntity + { + public long Id { get; set; } + } +} diff --git a/Models/TodoItem.cs b/EntityLayer/Entities/TodoItem.cs similarity index 58% rename from Models/TodoItem.cs rename to EntityLayer/Entities/TodoItem.cs index 1f6e5465..ad2942ae 100644 --- a/Models/TodoItem.cs +++ b/EntityLayer/Entities/TodoItem.cs @@ -1,9 +1,10 @@ -namespace TodoApi.Models +using TodoApi.EntityLayer.Entities.Abstract; + +namespace TodoApi.EntityLayer.Entities { #region snippet - public class TodoItem + public class TodoItem : BaseEntity { - public long Id { get; set; } public string Name { get; set; } public bool IsComplete { get; set; } public string Secret { get; set; } diff --git a/Startup.cs b/Startup.cs index 66f0ccc9..96d78b8d 100644 --- a/Startup.cs +++ b/Startup.cs @@ -1,17 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.HttpsPolicy; -using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using TodoApi.Models; +using TodoApi.DataAccessLayer; +using TodoApi.DataAccessLayer.Context; namespace TodoApi { @@ -29,6 +23,7 @@ public void ConfigureServices(IServiceCollection services) { services.AddDbContext(opt => opt.UseInMemoryDatabase("TodoList")); + services.AddDataRepositories(); services.AddControllers(); services.AddSwaggerGen(); } diff --git a/TodoApiDTO.csproj b/TodoApiDTO.csproj index f9ed1f08..fc50a2af 100644 --- a/TodoApiDTO.csproj +++ b/TodoApiDTO.csproj @@ -2,6 +2,7 @@ netcoreapp3.1 + $(MSBuildProjectName.Replace(" ", "_").Replace("DTO", "")) @@ -22,5 +23,9 @@ + + + + From 4b7620471f8f58e6476672cb3776f33209cc774f Mon Sep 17 00:00:00 2001 From: Evgenii Egorov Date: Thu, 27 Apr 2023 19:17:14 +0300 Subject: [PATCH 3/5] - Implemented a BusinessLayer to improve the separation of concerns ('TodoItemsService' is for CRUD with 'TodoItemDTO'); - Added 'AutoMapper' lib for 'TodoItem'<->'TodoItemDTO' bidirectional map. --- AutoMapperMapping/TodoProfile.cs | 15 +++++ BusinessLayer/Abstract/ITodoItemsService.cs | 16 ++++++ BusinessLayer/Concrete/TodoItemsService.cs | 64 +++++++++++++++++++++ BusinessLayer/DependencyInjection.cs | 17 ++++++ Startup.cs | 8 ++- TodoApiDTO.csproj | 6 +- 6 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 AutoMapperMapping/TodoProfile.cs create mode 100644 BusinessLayer/Abstract/ITodoItemsService.cs create mode 100644 BusinessLayer/Concrete/TodoItemsService.cs create mode 100644 BusinessLayer/DependencyInjection.cs diff --git a/AutoMapperMapping/TodoProfile.cs b/AutoMapperMapping/TodoProfile.cs new file mode 100644 index 00000000..9c0cb8d2 --- /dev/null +++ b/AutoMapperMapping/TodoProfile.cs @@ -0,0 +1,15 @@ +using AutoMapper; +using TodoApi.EntityLayer.Entities; +using TodoApi.Models; + + +namespace TodoApi.AutoMapperMapping +{ + public class TodoProfile : Profile + { + public TodoProfile() + { + CreateMap().ReverseMap(); + } + } +} \ No newline at end of file diff --git a/BusinessLayer/Abstract/ITodoItemsService.cs b/BusinessLayer/Abstract/ITodoItemsService.cs new file mode 100644 index 00000000..16b75257 --- /dev/null +++ b/BusinessLayer/Abstract/ITodoItemsService.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoApi.Models; + +namespace TodoApi.BusinessLayer.Abstract +{ + public interface ITodoItemsService + { + public Task CreateTodoAsync(TodoItemDTO item); + public Task GetTodoAsync(long id); + public Task> GetTodoList(); + public Task UpdateTodoAsync(long id, TodoItemDTO item); + public Task DeleteTodoAsync(long id); + + } +} diff --git a/BusinessLayer/Concrete/TodoItemsService.cs b/BusinessLayer/Concrete/TodoItemsService.cs new file mode 100644 index 00000000..bc930b00 --- /dev/null +++ b/BusinessLayer/Concrete/TodoItemsService.cs @@ -0,0 +1,64 @@ +using AutoMapper; +using System.Collections.Generic; +using System.Threading.Tasks; +using TodoApi.BusinessLayer.Abstract; +using TodoApi.DataAccessLayer.Abstract; +using TodoApi.EntityLayer.Entities; +using TodoApi.Models; + +namespace TodoApi.BusinessLayer.Concrete +{ + public class TodoItemsService : ITodoItemsService + { + private readonly ITodoItemDal _todoDal; + private readonly IMapper _mapper; + + public TodoItemsService(ITodoItemDal todoDal, IMapper mapper) + { + _todoDal = todoDal; + _mapper = mapper; + } + + public async Task CreateTodoAsync(TodoItemDTO item) + { + var itemToBeCreated = DTOToEntity(item); + var createdItem = await _todoDal.CreateAsync(itemToBeCreated); + var createdItemDTO = EntityToDTO(createdItem); + return createdItemDTO; + } + + public async Task DeleteTodoAsync(long id) + { + await _todoDal.DeleteAsync(id); + } + + public async Task GetTodoAsync(long id) + { + var item = await _todoDal.GetAsync(id); + var itemDto = EntityToDTO(item); + return itemDto; + } + + public async Task> GetTodoList() + { + var todoList = await _todoDal.GetAllAsync(); + var todoDTOList = _mapper.Map>(todoList); + return todoDTOList; + } + + public Task UpdateTodoAsync(long id, TodoItemDTO item) + { + var updatedItem = DTOToEntity(item); + return _todoDal.UpdateAsync(id, updatedItem); + + } + private TodoItemDTO EntityToDTO(TodoItem todoItem) + { + return _mapper.Map(todoItem); + } + private TodoItem DTOToEntity(TodoItemDTO todoItemDTO) + { + return _mapper.Map(todoItemDTO); + } + } +} diff --git a/BusinessLayer/DependencyInjection.cs b/BusinessLayer/DependencyInjection.cs new file mode 100644 index 00000000..5f574263 --- /dev/null +++ b/BusinessLayer/DependencyInjection.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using Microsoft.Extensions.DependencyInjection; +using TodoApi.BusinessLayer.Abstract; +using TodoApi.BusinessLayer.Concrete; + +namespace TodoApi.BusinessLayer +{ + public static class DependencyInjection + { + public static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddScoped(); + services.AddAutoMapper(typeof(DependencyInjection)); + return services; + } + } +} diff --git a/Startup.cs b/Startup.cs index 96d78b8d..947598f0 100644 --- a/Startup.cs +++ b/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using TodoApi.BusinessLayer; using TodoApi.DataAccessLayer; using TodoApi.DataAccessLayer.Context; @@ -23,7 +24,8 @@ public void ConfigureServices(IServiceCollection services) { services.AddDbContext(opt => opt.UseInMemoryDatabase("TodoList")); - services.AddDataRepositories(); + services.AddDataRepositories() + .AddApplication(); services.AddControllers(); services.AddSwaggerGen(); } @@ -35,8 +37,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); } - app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwagger() + .UseSwaggerUI(); app.UseHttpsRedirection(); diff --git a/TodoApiDTO.csproj b/TodoApiDTO.csproj index fc50a2af..7905945f 100644 --- a/TodoApiDTO.csproj +++ b/TodoApiDTO.csproj @@ -6,6 +6,8 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -23,9 +25,5 @@ - - - - From 3ab0f86c8ae52903013f0eeafa09ef0041eda0cd Mon Sep 17 00:00:00 2001 From: Evgenii Egorov Date: Thu, 27 Apr 2023 20:02:35 +0300 Subject: [PATCH 4/5] - Added logging to .txt (NLog lib); - Changed all 'TodoItemsController's methods to utilize exception handling with 'ExceptionLoggingMiddleware'. --- Controllers/TodoItemsController.cs | 84 +++-------------------- Middlewares/ExceptionLoggingMiddleware.cs | 61 ++++++++++++++++ Program.cs | 15 ++-- Startup.cs | 7 ++ TodoApiDTO.csproj | 1 + nlog.config | 31 +++++++++ 6 files changed, 119 insertions(+), 80 deletions(-) create mode 100644 Middlewares/ExceptionLoggingMiddleware.cs create mode 100644 nlog.config diff --git a/Controllers/TodoItemsController.cs b/Controllers/TodoItemsController.cs index c2cca5c3..a5c01187 100644 --- a/Controllers/TodoItemsController.cs +++ b/Controllers/TodoItemsController.cs @@ -1,10 +1,7 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using TodoApi.DataAccessLayer.Context; -using TodoApi.EntityLayer.Entities; +using TodoApi.BusinessLayer.Abstract; using TodoApi.Models; namespace TodoApi.Controllers @@ -13,106 +10,45 @@ namespace TodoApi.Controllers [ApiController] public class TodoItemsController : ControllerBase { - private readonly TodoContext _context; + private readonly ITodoItemsService manager; - public TodoItemsController(TodoContext context) + public TodoItemsController(ITodoItemsService todoManager) { - _context = context; + manager = todoManager; } [HttpGet] public async Task>> GetTodoItems() { - return await _context.TodoItems - .Select(x => ItemToDTO(x)) - .ToListAsync(); + return await manager.GetTodoList(); } [HttpGet("{id}")] public async Task> GetTodoItem(long id) { - var todoItem = await _context.TodoItems.FindAsync(id); - - if (todoItem == null) - { - return NotFound(); - } - - return ItemToDTO(todoItem); + return await manager.GetTodoAsync(id); } [HttpPut("{id}")] public async Task UpdateTodoItem(long id, TodoItemDTO todoItemDTO) { - if (id != todoItemDTO.Id) - { - return BadRequest(); - } - - var todoItem = await _context.TodoItems.FindAsync(id); - if (todoItem == null) - { - return NotFound(); - } - - todoItem.Name = todoItemDTO.Name; - todoItem.IsComplete = todoItemDTO.IsComplete; - - try - { - await _context.SaveChangesAsync(); - } - catch (DbUpdateConcurrencyException) when (!TodoItemExists(id)) - { - return NotFound(); - } - + await manager.UpdateTodoAsync(id, todoItemDTO); return NoContent(); } [HttpPost] public async Task> CreateTodoItem(TodoItemDTO todoItemDTO) { - var todoItem = new TodoItem - { - IsComplete = todoItemDTO.IsComplete, - Name = todoItemDTO.Name - }; - - _context.TodoItems.Add(todoItem); - await _context.SaveChangesAsync(); - - return CreatedAtAction( - nameof(GetTodoItem), - new { id = todoItem.Id }, - ItemToDTO(todoItem)); + var createdItem = await manager.CreateTodoAsync(todoItemDTO); + return CreatedAtAction(nameof(GetTodoItem), new { id = createdItem.Id }, createdItem); } [HttpDelete("{id}")] public async Task DeleteTodoItem(long id) { - var todoItem = await _context.TodoItems.FindAsync(id); - - if (todoItem == null) - { - return NotFound(); - } - - _context.TodoItems.Remove(todoItem); - await _context.SaveChangesAsync(); - + await manager.DeleteTodoAsync(id); return NoContent(); } - private bool TodoItemExists(long id) => - _context.TodoItems.Any(e => e.Id == id); - - private static TodoItemDTO ItemToDTO(TodoItem todoItem) => - new TodoItemDTO - { - Id = todoItem.Id, - Name = todoItem.Name, - IsComplete = todoItem.IsComplete - }; } } diff --git a/Middlewares/ExceptionLoggingMiddleware.cs b/Middlewares/ExceptionLoggingMiddleware.cs new file mode 100644 index 00000000..1a5b3297 --- /dev/null +++ b/Middlewares/ExceptionLoggingMiddleware.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; + +namespace TodoApi.Middlewares +{ + public class ExceptionLoggingMiddleware + { + private readonly ILogger _logger; + private readonly RequestDelegate _next; + + public ExceptionLoggingMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + _logger.LogError(ex,String.Empty,null); + await HandleException(context, ex.GetBaseException()); + } + } + + private static Task HandleException(HttpContext context, Exception ex) + { + + HttpStatusCode status; + if (ex is ArgumentException) + { + status = HttpStatusCode.BadRequest; + } + else if (ex is NullReferenceException || + ex is InvalidOperationException) + { + status = HttpStatusCode.NotFound; + } + else + { + status = HttpStatusCode.InternalServerError; + } + + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int)status; + var result = JsonSerializer.Serialize(new { title = status.ToString(), status, traceId = context.TraceIdentifier, message = ex.Message }); + + return context.Response.WriteAsync(result); + } + } + + +} diff --git a/Program.cs b/Program.cs index b27ac16a..1d4b980d 100644 --- a/Program.cs +++ b/Program.cs @@ -1,11 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using NLog.Extensions.Hosting; namespace TodoApi { @@ -13,7 +9,14 @@ public class Program { public static void Main(string[] args) { - CreateHostBuilder(args).Build().Run(); + CreateHostBuilder(args) + .ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.SetMinimumLevel(LogLevel.Error); + }) + .UseNLog() + .Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => diff --git a/Startup.cs b/Startup.cs index 947598f0..46e2bfae 100644 --- a/Startup.cs +++ b/Startup.cs @@ -7,6 +7,7 @@ using TodoApi.BusinessLayer; using TodoApi.DataAccessLayer; using TodoApi.DataAccessLayer.Context; +using TodoApi.Middlewares; namespace TodoApi { @@ -24,9 +25,12 @@ public void ConfigureServices(IServiceCollection services) { services.AddDbContext(opt => opt.UseInMemoryDatabase("TodoList")); + services.AddDataRepositories() .AddApplication(); + services.AddControllers(); + services.AddSwaggerGen(); } @@ -37,6 +41,9 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseDeveloperExceptionPage(); } + + app.UseMiddleware(); + app.UseSwagger() .UseSwaggerUI(); diff --git a/TodoApiDTO.csproj b/TodoApiDTO.csproj index 7905945f..5bfac8df 100644 --- a/TodoApiDTO.csproj +++ b/TodoApiDTO.csproj @@ -15,6 +15,7 @@ + diff --git a/nlog.config b/nlog.config new file mode 100644 index 00000000..35ebb751 --- /dev/null +++ b/nlog.config @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 77bc0cea718d262769d7949013e277b3a2045b9d Mon Sep 17 00:00:00 2001 From: Evgenii Egorov Date: Thu, 27 Apr 2023 20:47:36 +0300 Subject: [PATCH 5/5] - Changed storage from 'InMemoryDB' to 'MS SQL Server'; - Connection string is stored in 'appsettings.json' file. --- ...0230427172404_InitialMigration.Designer.cs | 46 +++++++++++++++++++ Migrations/20230427172404_InitialMigration.cs | 31 +++++++++++++ Migrations/TodoContextModelSnapshot.cs | 44 ++++++++++++++++++ Startup.cs | 4 +- TodoApiDTO.csproj | 4 ++ appsettings.json | 3 ++ 6 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 Migrations/20230427172404_InitialMigration.Designer.cs create mode 100644 Migrations/20230427172404_InitialMigration.cs create mode 100644 Migrations/TodoContextModelSnapshot.cs diff --git a/Migrations/20230427172404_InitialMigration.Designer.cs b/Migrations/20230427172404_InitialMigration.Designer.cs new file mode 100644 index 00000000..689518c8 --- /dev/null +++ b/Migrations/20230427172404_InitialMigration.Designer.cs @@ -0,0 +1,46 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoApi.DataAccessLayer.Context; + +namespace TodoApi.Migrations +{ + [DbContext(typeof(TodoContext))] + [Migration("20230427172404_InitialMigration")] + partial class InitialMigration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("TodoApi.EntityLayer.Entities.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Secret") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20230427172404_InitialMigration.cs b/Migrations/20230427172404_InitialMigration.cs new file mode 100644 index 00000000..c1beff77 --- /dev/null +++ b/Migrations/20230427172404_InitialMigration.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace TodoApi.Migrations +{ + public partial class InitialMigration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "TodoItems", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(nullable: true), + IsComplete = table.Column(nullable: false), + Secret = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItems", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TodoItems"); + } + } +} diff --git a/Migrations/TodoContextModelSnapshot.cs b/Migrations/TodoContextModelSnapshot.cs new file mode 100644 index 00000000..feb5425a --- /dev/null +++ b/Migrations/TodoContextModelSnapshot.cs @@ -0,0 +1,44 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoApi.DataAccessLayer.Context; + +namespace TodoApi.Migrations +{ + [DbContext(typeof(TodoContext))] + partial class TodoContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.0") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("TodoApi.EntityLayer.Entities.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Secret") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Startup.cs b/Startup.cs index 46e2bfae..235a44ee 100644 --- a/Startup.cs +++ b/Startup.cs @@ -23,8 +23,10 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + //services.AddDbContext(opt => + // opt.UseInMemoryDatabase("TodoList")); services.AddDbContext(opt => - opt.UseInMemoryDatabase("TodoList")); + opt.UseSqlServer(Configuration.GetConnectionString("Database"))); services.AddDataRepositories() .AddApplication(); diff --git a/TodoApiDTO.csproj b/TodoApiDTO.csproj index 5bfac8df..c60cc90b 100644 --- a/TodoApiDTO.csproj +++ b/TodoApiDTO.csproj @@ -14,6 +14,10 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/appsettings.json b/appsettings.json index d9d9a9bf..f814e77a 100644 --- a/appsettings.json +++ b/appsettings.json @@ -1,4 +1,7 @@ { + "ConnectionStrings": { + "Database": "Data Source=localhost\\SQLEXPRESS;Initial Catalog=TodoList;Integrated Security=True;" + }, "Logging": { "LogLevel": { "Default": "Information",