diff --git a/Controllers/TodoItemsController.cs b/Controllers/TodoItemsController.cs deleted file mode 100644 index 0ef138e7..00000000 --- a/Controllers/TodoItemsController.cs +++ /dev/null @@ -1,116 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using TodoApi.Models; - -namespace TodoApi.Controllers -{ - [Route("api/[controller]")] - [ApiController] - public class TodoItemsController : ControllerBase - { - private readonly TodoContext _context; - - public TodoItemsController(TodoContext context) - { - _context = context; - } - - [HttpGet] - public async Task>> GetTodoItems() - { - return await _context.TodoItems - .Select(x => ItemToDTO(x)) - .ToListAsync(); - } - - [HttpGet("{id}")] - public async Task> GetTodoItem(long id) - { - var todoItem = await _context.TodoItems.FindAsync(id); - - if (todoItem == null) - { - return NotFound(); - } - - return ItemToDTO(todoItem); - } - - [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(); - } - - 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)); - } - - [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(); - - 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/Program.cs b/Program.cs deleted file mode 100644 index b27ac16a..00000000 --- a/Program.cs +++ /dev/null @@ -1,26 +0,0 @@ -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; - -namespace TodoApi -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } -} diff --git a/TodoApi.BLL/Infrastructure/ValidationException.cs b/TodoApi.BLL/Infrastructure/ValidationException.cs new file mode 100644 index 00000000..088bbee2 --- /dev/null +++ b/TodoApi.BLL/Infrastructure/ValidationException.cs @@ -0,0 +1,12 @@ + +namespace TodoApi.BLL.Infrastructure +{ + public class ValidationException : Exception + { + public string Property { get; protected set; } + public ValidationException(string message, string prop) : base(message) + { + Property = prop; + } + } +} diff --git a/TodoApi.BLL/Interfaces/ITodoItemService.cs b/TodoApi.BLL/Interfaces/ITodoItemService.cs new file mode 100644 index 00000000..fd697ba7 --- /dev/null +++ b/TodoApi.BLL/Interfaces/ITodoItemService.cs @@ -0,0 +1,14 @@ +using TodoApi.DTO; + +namespace TodoApi.BLL.Interfaces +{ + public interface ITodoItemService + { + Task GetTodoItemAsync(long id); + Task> GetTodoItemsAsync(); + Task UpdateTodoItemAsync(long id, TodoItemDTO todoItemDTO); + Task CreateTodoItemAsync(TodoItemDTO todoItemDTO); + Task DeleteTodoItemAsync(long id); + void Dispose(); + } +} diff --git a/TodoApi.BLL/Services/TodoItemService.cs b/TodoApi.BLL/Services/TodoItemService.cs new file mode 100644 index 00000000..a37b57f9 --- /dev/null +++ b/TodoApi.BLL/Services/TodoItemService.cs @@ -0,0 +1,85 @@ +using TodoApi.BLL.Interfaces; +using TodoApi.DAL.Entities; +using TodoApi.DAL.Extensions; +using TodoApi.DAL.Interfaces; +using TodoApi.DTO; + +namespace TodoApi.BLL.Services +{ + public class TodoItemService : ITodoItemService + { + IUnitOfWork Database { get; set; } + + public TodoItemService(IUnitOfWork uow) + { + Database = uow; + } + + public async Task GetTodoItemAsync(long id) + { + var todoItem = await Database.TodoItems.GetTodoItemAsync(id); + + if (todoItem == null) + { + throw new KeyNotFoundException("Item not found!"); + } + return todoItem.ItemToDTO(); + } + + public async Task> GetTodoItemsAsync() + { + var todoItems = await Database.TodoItems.GetTodoItemsAsync(); + + return todoItems.Select(x => x.ItemToDTO()).ToList(); + } + + public async Task UpdateTodoItemAsync(long id, TodoItemDTO todoItemDTO) + { + if (id != todoItemDTO.Id) + { + throw new Exception("Different object IDs!"); + } + + var todoItem = await Database.TodoItems.GetTodoItemAsync(id); + if (todoItem == null) + { + throw new KeyNotFoundException("Item not found!"); + } + + todoItem.Name = todoItemDTO.Name; + todoItem.IsComplete = todoItemDTO.IsComplete; + + await Database.TodoItems.UpdateTodoItemAsync(todoItem); + } + + public async Task CreateTodoItemAsync(TodoItemDTO todoItemDTO) + { + + var todoItem = new TodoItem + { + Name = todoItemDTO.Name, + IsComplete = todoItemDTO.IsComplete, + Secret = string.Empty + }; + + return (await Database.TodoItems.CreateTodoItemAsync(todoItem)).ItemToDTO(); + } + + public async Task DeleteTodoItemAsync(long id) + { + var todoItem = await Database.TodoItems.GetTodoItemAsync(id); + + if (todoItem == null) + { + throw new KeyNotFoundException("Item not found!"); + } + + await Database.TodoItems.DeleteTodoItemAsync(id); + } + + public void Dispose() + { + Database.Dispose(); + } + } +} diff --git a/TodoApi.BLL/TodoApi - Backup.BLL.csproj b/TodoApi.BLL/TodoApi - Backup.BLL.csproj new file mode 100644 index 00000000..451ff2c6 --- /dev/null +++ b/TodoApi.BLL/TodoApi - Backup.BLL.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/TodoApi.BLL/TodoApi.BLL.csproj b/TodoApi.BLL/TodoApi.BLL.csproj new file mode 100644 index 00000000..c1f70f88 --- /dev/null +++ b/TodoApi.BLL/TodoApi.BLL.csproj @@ -0,0 +1,20 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Models/TodoContext.cs b/TodoApi.DAL/DBContext/TodoContext.cs similarity index 81% rename from Models/TodoContext.cs rename to TodoApi.DAL/DBContext/TodoContext.cs index 6e59e363..05758eab 100644 --- a/Models/TodoContext.cs +++ b/TodoApi.DAL/DBContext/TodoContext.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; +using TodoApi.DAL.Entities; -namespace TodoApi.Models +namespace TodoApi.DAL.DBContext { public class TodoContext : DbContext { diff --git a/Models/TodoItem.cs b/TodoApi.DAL/Entities/TodoItem.cs similarity index 76% rename from Models/TodoItem.cs rename to TodoApi.DAL/Entities/TodoItem.cs index 1f6e5465..929c6748 100644 --- a/Models/TodoItem.cs +++ b/TodoApi.DAL/Entities/TodoItem.cs @@ -1,6 +1,6 @@ -namespace TodoApi.Models + +namespace TodoApi.DAL.Entities { - #region snippet public class TodoItem { public long Id { get; set; } @@ -8,5 +8,4 @@ public class TodoItem public bool IsComplete { get; set; } public string Secret { get; set; } } - #endregion } \ No newline at end of file diff --git a/TodoApi.DAL/Extensions/TodoItemExtension.cs b/TodoApi.DAL/Extensions/TodoItemExtension.cs new file mode 100644 index 00000000..4ad7f8f6 --- /dev/null +++ b/TodoApi.DAL/Extensions/TodoItemExtension.cs @@ -0,0 +1,16 @@ +using TodoApi.DTO; +using TodoApi.DAL.Entities; + +namespace TodoApi.DAL.Extensions +{ + public static class TodoItemExtension + { + public static TodoItemDTO ItemToDTO(this TodoItem todoItem) => + new TodoItemDTO + { + Id = todoItem.Id, + Name = todoItem.Name, + IsComplete = todoItem.IsComplete + }; + } +} diff --git a/TodoApi.DAL/Interfaces/IRepository.cs b/TodoApi.DAL/Interfaces/IRepository.cs new file mode 100644 index 00000000..f1f6a80b --- /dev/null +++ b/TodoApi.DAL/Interfaces/IRepository.cs @@ -0,0 +1,12 @@ + +namespace TodoApi.DAL.Interfaces +{ + public interface IRepository where T : class + { + Task GetTodoItemAsync(long id); + Task> GetTodoItemsAsync(); + Task UpdateTodoItemAsync(T todoItemDTO); + Task CreateTodoItemAsync(T todoItemDTO); + Task DeleteTodoItemAsync(long id); + } +} diff --git a/TodoApi.DAL/Interfaces/IUnitOfWork.cs b/TodoApi.DAL/Interfaces/IUnitOfWork.cs new file mode 100644 index 00000000..20577895 --- /dev/null +++ b/TodoApi.DAL/Interfaces/IUnitOfWork.cs @@ -0,0 +1,10 @@ + +using TodoApi.DAL.Entities; + +namespace TodoApi.DAL.Interfaces +{ + public interface IUnitOfWork : IDisposable + { + IRepository TodoItems { get; } + } +} diff --git a/TodoApi.DAL/Migrations/20230818145516_TodoItemDAL_1.Designer.cs b/TodoApi.DAL/Migrations/20230818145516_TodoItemDAL_1.Designer.cs new file mode 100644 index 00000000..cff6dec2 --- /dev/null +++ b/TodoApi.DAL/Migrations/20230818145516_TodoItemDAL_1.Designer.cs @@ -0,0 +1,53 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoApi.DAL.DBContext; + +#nullable disable + +namespace TodoApi.DAL.Migrations +{ + [DbContext(typeof(TodoContext))] + [Migration("20230818145516_TodoItemDAL_1")] + partial class TodoItemDAL_1 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TodoApi.DAL.Entities.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Secret") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TodoApi.DAL/Migrations/20230818145516_TodoItemDAL_1.cs b/TodoApi.DAL/Migrations/20230818145516_TodoItemDAL_1.cs new file mode 100644 index 00000000..bf8cf104 --- /dev/null +++ b/TodoApi.DAL/Migrations/20230818145516_TodoItemDAL_1.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TodoApi.DAL.Migrations +{ + /// + public partial class TodoItemDAL_1 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "TodoItems", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + IsComplete = table.Column(type: "bit", nullable: false), + Secret = table.Column(type: "nvarchar(max)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_TodoItems", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TodoItems"); + } + } +} diff --git a/TodoApi.DAL/Migrations/TodoContextModelSnapshot.cs b/TodoApi.DAL/Migrations/TodoContextModelSnapshot.cs new file mode 100644 index 00000000..9f72e426 --- /dev/null +++ b/TodoApi.DAL/Migrations/TodoContextModelSnapshot.cs @@ -0,0 +1,50 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TodoApi.DAL.DBContext; + +#nullable disable + +namespace TodoApi.DAL.Migrations +{ + [DbContext(typeof(TodoContext))] + partial class TodoContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("TodoApi.DAL.Entities.TodoItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("IsComplete") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Secret") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TodoItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TodoApi.DAL/Repositories/EFUnitOfWork.cs b/TodoApi.DAL/Repositories/EFUnitOfWork.cs new file mode 100644 index 00000000..7e35a2c1 --- /dev/null +++ b/TodoApi.DAL/Repositories/EFUnitOfWork.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TodoApi.DAL.DBContext; +using TodoApi.DAL.Entities; +using TodoApi.DAL.Interfaces; + +namespace TodoApi.DAL.Repositories +{ + public class EFUnitOfWork : IUnitOfWork + { + private TodoContext db; + private TodoItemRepository todoItemRepository; + + public EFUnitOfWork(DbContextOptions options) + { + db = new TodoContext(options); + } + public IRepository TodoItems + { + get + { + if (todoItemRepository == null) + todoItemRepository = new TodoItemRepository(db); + return todoItemRepository; + } + } + + private bool disposed = false; + + public virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + db.Dispose(); + } + this.disposed = true; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/TodoApi.DAL/Repositories/TodoItemRepository.cs b/TodoApi.DAL/Repositories/TodoItemRepository.cs new file mode 100644 index 00000000..e3338018 --- /dev/null +++ b/TodoApi.DAL/Repositories/TodoItemRepository.cs @@ -0,0 +1,55 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using TodoApi.DAL.DBContext; +using TodoApi.DAL.Entities; +using TodoApi.DAL.Interfaces; + +namespace TodoApi.DAL.Repositories +{ + public class TodoItemRepository : IRepository + { + private TodoContext db; + + public TodoItemRepository(TodoContext context) + { + this.db = context; + } + + public async Task> GetTodoItemsAsync() + { + return await db.TodoItems.ToListAsync(); + } + + public async Task GetTodoItemAsync(long id) + { + return await db.TodoItems.FirstOrDefaultAsync(x => x.Id == id); + } + + public async Task CreateTodoItemAsync(TodoItem todoItem) + { + db.TodoItems.Add(todoItem); + await db.SaveChangesAsync(); + + return todoItem; + } + + public async Task UpdateTodoItemAsync(TodoItem todoItem) + { + db.Entry(todoItem).State = EntityState.Modified; + + await db.SaveChangesAsync(); + } + + public async Task DeleteTodoItemAsync(long id) + { + TodoItem todoItem = await db.TodoItems.FindAsync(id); + if (todoItem != null) + db.TodoItems.Remove(todoItem); + await db.SaveChangesAsync(); + } + } +} diff --git a/TodoApi.DAL/TodoApi.DAL.csproj b/TodoApi.DAL/TodoApi.DAL.csproj new file mode 100644 index 00000000..90966ffe --- /dev/null +++ b/TodoApi.DAL/TodoApi.DAL.csproj @@ -0,0 +1,31 @@ + + + + net7.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + diff --git a/TodoApi.DTO/TodoApi.DTO.csproj b/TodoApi.DTO/TodoApi.DTO.csproj new file mode 100644 index 00000000..293106a9 --- /dev/null +++ b/TodoApi.DTO/TodoApi.DTO.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Models/TodoItemDTO.cs b/TodoApi.DTO/TodoItemDto.cs similarity index 72% rename from Models/TodoItemDTO.cs rename to TodoApi.DTO/TodoItemDto.cs index e66a500a..acfdb09a 100644 --- a/Models/TodoItemDTO.cs +++ b/TodoApi.DTO/TodoItemDto.cs @@ -1,11 +1,10 @@ -namespace TodoApi.Models + +namespace TodoApi.DTO { - #region snippet public class TodoItemDTO { public long Id { get; set; } public string Name { get; set; } public bool IsComplete { get; set; } } - #endregion } diff --git a/TodoApi/Controllers/TodoItemsController.cs b/TodoApi/Controllers/TodoItemsController.cs new file mode 100644 index 00000000..8eab309c --- /dev/null +++ b/TodoApi/Controllers/TodoItemsController.cs @@ -0,0 +1,55 @@ +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using TodoApi.BLL.Interfaces; +using TodoApi.DTO; + +namespace TodoApi.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class TodoItemsController : ControllerBase + { + + private readonly ITodoItemService _todoItemService; + + public TodoItemsController(ITodoItemService todoItemService) + { + _todoItemService = todoItemService; + } + + [HttpGet] + public async Task>> GetTodoItems() + { + return (await _todoItemService.GetTodoItemsAsync()).ToList(); + } + + [HttpGet("{id}")] + public async Task> GetTodoItem(long id) + { + return await _todoItemService.GetTodoItemAsync(id); + } + + [HttpPut("{id}")] + public async Task UpdateTodoItem(long id, TodoItemDTO todoItemDTO) + { + await _todoItemService.UpdateTodoItemAsync(id, todoItemDTO); + return Ok(); + } + + [HttpPost] + public async Task> CreateTodoItem(TodoItemDTO todoItemDTO) + { + return await _todoItemService.CreateTodoItemAsync(todoItemDTO); + } + + [HttpDelete("{id}")] + public async Task DeleteTodoItem(long id) + { + await _todoItemService.DeleteTodoItemAsync(id); + + return Ok(); + } + } +} diff --git a/TodoApi/Logger/ExceptionHandler.cs b/TodoApi/Logger/ExceptionHandler.cs new file mode 100644 index 00000000..be373163 --- /dev/null +++ b/TodoApi/Logger/ExceptionHandler.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json; +using System.Net; +using System.Threading.Tasks; +using System; +using TodoApi.BLL.Infrastructure; +using NLog; + +namespace TodoApi.Logger +{ + public class ExceptionHandler + { + private readonly RequestDelegate _next; + + public ExceptionHandler(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception ex) + { + await HandleExceptionAsync(context, ex); + } + } + + private static Task HandleExceptionAsync(HttpContext context, Exception exception) + { + HttpStatusCode status = HttpStatusCode.NotFound; + string stackTrace = exception.StackTrace; + string message = exception.Message; + + var exceptionType = exception.GetType(); + + if (exceptionType == typeof(ValidationException)) + { + LogManager.GetLogger("CustomError").Error(exception + Environment.NewLine); + } + else if (exceptionType != typeof(DbUpdateConcurrencyException)) + { + status = exceptionType != typeof(DbUpdateConcurrencyException)? HttpStatusCode.InternalServerError: status; + LogManager.GetLogger("SystemError").Error(exception + Environment.NewLine); + } + + var exceptionResult = JsonConvert.SerializeObject( + new + { + error = message, + stackTrace + }); + + context.Response.ContentType = "application/json"; + context.Response.StatusCode = (int)status; + + return context.Response.WriteAsync(exceptionResult); + } + } +} diff --git a/TodoApi/Models/TodoItem.cs b/TodoApi/Models/TodoItem.cs new file mode 100644 index 00000000..ca8650e1 --- /dev/null +++ b/TodoApi/Models/TodoItem.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; + +namespace TodoApi.Models +{ + #region snippet + public class TodoItem + { + [Required] + public long Id { get; set; } + [Required] + [MaxLength(100)] + public string Name { get; set; } + [Required] + public bool IsComplete { get; set; } + public string Secret { get; set; } + } + #endregion +} \ No newline at end of file diff --git a/TodoApi/Program.cs b/TodoApi/Program.cs new file mode 100644 index 00000000..a17468b4 --- /dev/null +++ b/TodoApi/Program.cs @@ -0,0 +1,50 @@ +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; +using NLog.Web; + +namespace TodoApi +{ + public class Program + { + public static void Main(string[] args) + { + var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); + + try + { + logger.Debug("init main"); + CreateHostBuilder(args).Build().Run(); + } + catch (Exception exception) + { + //NLog: catch setup errors + logger.Error(exception, "Stopped program because of exception"); + throw; + } + finally + { + // Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux) + NLog.LogManager.Shutdown(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }).ConfigureLogging(logging => + { + logging.ClearProviders(); + logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); + }) + .UseNLog(); + } +} diff --git a/Properties/launchSettings.json b/TodoApi/Properties/launchSettings.json similarity index 100% rename from Properties/launchSettings.json rename to TodoApi/Properties/launchSettings.json diff --git a/README.md b/TodoApi/README.md similarity index 100% rename from README.md rename to TodoApi/README.md diff --git a/Startup.cs b/TodoApi/Startup.cs similarity index 61% rename from Startup.cs rename to TodoApi/Startup.cs index bbfbc83d..fa42f916 100644 --- a/Startup.cs +++ b/TodoApi/Startup.cs @@ -1,17 +1,13 @@ -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 Microsoft.OpenApi.Models; +using System; +using TodoApi.DAL.DBContext; +using TodoApi.Logger; namespace TodoApi { @@ -27,9 +23,16 @@ 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(options => + options.UseSqlServer(Configuration.GetConnectionString("DbConnection") ?? + throw new InvalidOperationException("Connection string 'DbConnection' not found."))); + services.AddControllers(); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Todo API", Version = "v1" }); + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -50,6 +53,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { endpoints.MapControllers(); }); + + app.UseSwagger(); + + app.UseSwaggerUI(settings => + { + settings.SwaggerEndpoint("/swagger/v1/swagger.json", "v1"); + settings.RoutePrefix = string.Empty; + + }); + + app.UseMiddleware(); } } } diff --git a/TodoApi/TodoApi.csproj b/TodoApi/TodoApi.csproj new file mode 100644 index 00000000..35254501 --- /dev/null +++ b/TodoApi/TodoApi.csproj @@ -0,0 +1,37 @@ + + + + net7.0 + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + + diff --git a/TodoApi/TodoApiDTO.sln b/TodoApi/TodoApiDTO.sln new file mode 100644 index 00000000..e49c182b --- /dev/null +++ b/TodoApi/TodoApiDTO.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30002.166 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApiDTO", "TodoApiDTO.csproj", "{623124F9-F5BA-42DD-BC26-A1720774229C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {623124F9-F5BA-42DD-BC26-A1720774229C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {623124F9-F5BA-42DD-BC26-A1720774229C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {623124F9-F5BA-42DD-BC26-A1720774229C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {623124F9-F5BA-42DD-BC26-A1720774229C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {60984BC9-01D1-4B40-9733-E459B1231F96} + EndGlobalSection +EndGlobal diff --git a/appsettings.Development.json b/TodoApi/appsettings.Development.json similarity index 100% rename from appsettings.Development.json rename to TodoApi/appsettings.Development.json diff --git a/TodoApi/appsettings.json b/TodoApi/appsettings.json new file mode 100644 index 00000000..8d4d36b1 --- /dev/null +++ b/TodoApi/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DbConnection": "Server=(localdb)\\MSSQLLocalDB; Database=TodoApp; Trusted_Connection=true; MultipleActiveResultSets=true; Integrated Security=true;" + } +} diff --git a/TodoApi/nlog.config b/TodoApi/nlog.config new file mode 100644 index 00000000..541d03a5 --- /dev/null +++ b/TodoApi/nlog.config @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TodoApiDTO.csproj b/TodoApiDTO.csproj deleted file mode 100644 index bba6f6af..00000000 --- a/TodoApiDTO.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp3.1 - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/TodoApiDTO.sln b/TodoApiDTO.sln index e49c182b..7537ebd4 100644 --- a/TodoApiDTO.sln +++ b/TodoApiDTO.sln @@ -1,9 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30002.166 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.34003.232 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApiDTO", "TodoApiDTO.csproj", "{623124F9-F5BA-42DD-BC26-A1720774229C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApi.DTO", "TodoApi.DTO\TodoApi.DTO.csproj", "{EB567F3C-9932-4B18-B8AD-FC461ECA0A96}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApi.BLL", "TodoApi.BLL\TodoApi.BLL.csproj", "{0E77E7F2-BE6C-4A3E-A469-9471336A5053}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApi", "TodoApi\TodoApi.csproj", "{7D7BF2C5-FD8F-4FE8-9024-02082C03852C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TodoApi.DAL", "TodoApi.DAL\TodoApi.DAL.csproj", "{501DEE26-ED46-4905-9FAA-9FA7FF84568B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -11,10 +17,22 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {623124F9-F5BA-42DD-BC26-A1720774229C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {623124F9-F5BA-42DD-BC26-A1720774229C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {623124F9-F5BA-42DD-BC26-A1720774229C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {623124F9-F5BA-42DD-BC26-A1720774229C}.Release|Any CPU.Build.0 = Release|Any CPU + {EB567F3C-9932-4B18-B8AD-FC461ECA0A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB567F3C-9932-4B18-B8AD-FC461ECA0A96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB567F3C-9932-4B18-B8AD-FC461ECA0A96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB567F3C-9932-4B18-B8AD-FC461ECA0A96}.Release|Any CPU.Build.0 = Release|Any CPU + {0E77E7F2-BE6C-4A3E-A469-9471336A5053}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E77E7F2-BE6C-4A3E-A469-9471336A5053}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E77E7F2-BE6C-4A3E-A469-9471336A5053}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E77E7F2-BE6C-4A3E-A469-9471336A5053}.Release|Any CPU.Build.0 = Release|Any CPU + {7D7BF2C5-FD8F-4FE8-9024-02082C03852C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7D7BF2C5-FD8F-4FE8-9024-02082C03852C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7D7BF2C5-FD8F-4FE8-9024-02082C03852C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7D7BF2C5-FD8F-4FE8-9024-02082C03852C}.Release|Any CPU.Build.0 = Release|Any CPU + {501DEE26-ED46-4905-9FAA-9FA7FF84568B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {501DEE26-ED46-4905-9FAA-9FA7FF84568B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {501DEE26-ED46-4905-9FAA-9FA7FF84568B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {501DEE26-ED46-4905-9FAA-9FA7FF84568B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/appsettings.json b/appsettings.json deleted file mode 100644 index d9d9a9bf..00000000 --- a/appsettings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "AllowedHosts": "*" -}