From 68da43a673231456140458a25da9b7e896e51d56 Mon Sep 17 00:00:00 2001 From: Dmitrii Kollerov Date: Fri, 25 Sep 2026 08:05:46 +0300 Subject: [PATCH] init --- Aggregators/BookAggregator.cs | 63 ++++++++++++++++ Controllers/BookController.cs | 60 ++++++++++++++++ Database/Connector/AppDbContext.cs | 36 ++++++++++ Dto/BookDto.cs | 14 ++++ ErrorLogMiddleware.cs | 27 +++++++ Filters/XmlAttributeFilter.cs | 49 +++++++++++++ Interfaces/IBookAggregator.cs | 14 ++++ Interfaces/IBookRepository.cs | 14 ++++ Interfaces/IBookService.cs | 14 ++++ Migrations/20260925040202_init.Designer.cs | 56 +++++++++++++++ Migrations/20260925040202_init.cs | 37 ++++++++++ Migrations/AppDbContextModelSnapshot.cs | 53 ++++++++++++++ Models/Mapper/BookMapper.cs | 32 +++++++++ Models/Mapper/RequestMapper.cs | 30 ++++++++ Models/Model/BookCreateModel.cs | 11 +++ Models/Model/BookModel.cs | 12 ++++ Models/Model/BookUpdateModel.cs | 11 +++ Models/Request/BookCreateRequest.cs | 17 +++++ Models/Request/BookFilterRequest.cs | 10 +++ Models/Request/BookUpdateRequest.cs | 19 +++++ Models/Response/BookListResponse.cs | 9 +++ Models/Response/BookOperationResponse.cs | 8 +++ Models/Response/BookSingleResponse.cs | 9 +++ Program.cs | 83 ++++++++++++++++++++++ Properties/launchSettings.json | 23 ++++++ README.md | 4 +- Repositories/BookRepository.cs | 70 ++++++++++++++++++ Services/BookService.cs | 61 ++++++++++++++++ example-csharp-app.csproj | 23 ++++++ 29 files changed, 867 insertions(+), 2 deletions(-) create mode 100644 Aggregators/BookAggregator.cs create mode 100644 Controllers/BookController.cs create mode 100644 Database/Connector/AppDbContext.cs create mode 100644 Dto/BookDto.cs create mode 100644 ErrorLogMiddleware.cs create mode 100644 Filters/XmlAttributeFilter.cs create mode 100644 Interfaces/IBookAggregator.cs create mode 100644 Interfaces/IBookRepository.cs create mode 100644 Interfaces/IBookService.cs create mode 100644 Migrations/20260925040202_init.Designer.cs create mode 100644 Migrations/20260925040202_init.cs create mode 100644 Migrations/AppDbContextModelSnapshot.cs create mode 100644 Models/Mapper/BookMapper.cs create mode 100644 Models/Mapper/RequestMapper.cs create mode 100644 Models/Model/BookCreateModel.cs create mode 100644 Models/Model/BookModel.cs create mode 100644 Models/Model/BookUpdateModel.cs create mode 100644 Models/Request/BookCreateRequest.cs create mode 100644 Models/Request/BookFilterRequest.cs create mode 100644 Models/Request/BookUpdateRequest.cs create mode 100644 Models/Response/BookListResponse.cs create mode 100644 Models/Response/BookOperationResponse.cs create mode 100644 Models/Response/BookSingleResponse.cs create mode 100644 Program.cs create mode 100644 Properties/launchSettings.json create mode 100644 Repositories/BookRepository.cs create mode 100644 Services/BookService.cs create mode 100644 example-csharp-app.csproj diff --git a/Aggregators/BookAggregator.cs b/Aggregators/BookAggregator.cs new file mode 100644 index 0000000..573f57f --- /dev/null +++ b/Aggregators/BookAggregator.cs @@ -0,0 +1,63 @@ +using System; +using example_csharp_app.Interfaces; +using example_csharp_app.Models.Mapper; +using example_csharp_app.Models.Request; +using example_csharp_app.Models.Response; + +namespace example_csharp_app.Aggregators; + +public class BookAggregator : IBookAggregator +{ + private readonly IBookService _bookService; + + public BookAggregator(IBookService bookService) + { + _bookService = bookService; + } + + public async Task AddBookAsync(BookCreateRequest request) + { + var result = await _bookService.AddBookAsync(request.MapToModel()); + return new BookOperationResponse() + { + IsSuccess = result > 0 + }; + } + + public async Task GetBookAsync(Guid bookId) + { + var book = await _bookService.GetBookAsync(bookId); + return new BookSingleResponse() + { + Book = book + }; + } + + public async Task GetBookListAsync(BookFilterRequest request) + { + // should make separate filter object, but feel lazy + var bookList = await _bookService.GetBooksAsync(request); + return new BookListResponse() + { + Books = bookList + }; + } + + public async Task RemoveBookAsync(Guid bookId) + { + var result = await _bookService.RemoveBookAsync(bookId); + return new BookOperationResponse() + { + IsSuccess = result > 0 + }; + } + + public async Task UpdateBookAsync(BookUpdateRequest request) + { + var result = await _bookService.UpdateBookAsync(request.Id, request.MapToModel()); + return new BookOperationResponse() + { + IsSuccess = result > 0 + }; + } +} diff --git a/Controllers/BookController.cs b/Controllers/BookController.cs new file mode 100644 index 0000000..cc09ac0 --- /dev/null +++ b/Controllers/BookController.cs @@ -0,0 +1,60 @@ +using example_csharp_app.Interfaces; +using example_csharp_app.Models.Request; +using example_csharp_app.Models.Response; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; + +namespace example_csharp_app.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class BookController : ControllerBase + { + private readonly IBookAggregator _bookAggregator; + + public BookController(IBookAggregator bookAggregator) + { + _bookAggregator = bookAggregator; + } + + [SwaggerResponse(200, "Получить список книг", typeof(BookListResponse))] + [HttpGet()] + public async Task GetList([FromQuery]BookFilterRequest request) + { + var bookList = await _bookAggregator.GetBookListAsync(request); + return Ok(bookList); + } + + [SwaggerResponse(200, "Получить список книг", typeof(BookSingleResponse))] + [HttpGet("{bookId}")] + public async Task GetBook([FromRoute] Guid bookId) + { + var result = await _bookAggregator.GetBookAsync(bookId); + return Ok(result); + } + + [SwaggerResponse(200, "Добавить книгу", typeof(BookOperationResponse))] + [HttpPost()] + public async Task AddBook([FromBody]BookCreateRequest request) + { + var result = await _bookAggregator.AddBookAsync(request); + return Ok(result); + } + + [SwaggerResponse(200, "Обновить книгу", typeof(BookOperationResponse))] + [HttpPut()] + public async Task UpdateBook([FromBody]BookUpdateRequest request) + { + var result = await _bookAggregator.UpdateBookAsync(request); + return Ok(result); + } + + [SwaggerResponse(200, "Удалить книгу", typeof(BookOperationResponse))] + [HttpDelete("{bookId}")] + public async Task RemoveBook([FromRoute]Guid bookId) + { + var result = await _bookAggregator.RemoveBookAsync(bookId); + return Ok(result); + } + } +} diff --git a/Database/Connector/AppDbContext.cs b/Database/Connector/AppDbContext.cs new file mode 100644 index 0000000..1f0430e --- /dev/null +++ b/Database/Connector/AppDbContext.cs @@ -0,0 +1,36 @@ +using example_csharp_app.Dto; +using Microsoft.EntityFrameworkCore; +using System; + +namespace example_app.Database.Connector; + +public class AppDbContext(DbContextOptions options) : DbContext(options) +{ + public DbSet Books { set; get; } + + protected override void OnModelCreating(ModelBuilder builder) + { + base.OnModelCreating(builder); + + builder.Entity() + .ToTable("Books") + .HasKey(x => x.Id); + + builder.Entity() + .Property(x => x.Author) + .IsRequired(); + builder.Entity() + .Property(x => x.Title) + .IsRequired(); + builder.Entity() + .Property(x => x.YearPublished) + .IsRequired(); + builder.Entity() + .Property(x => x.BookIndex) + .IsRequired(); + + builder.Entity() + .Property(x => x.BookIndex) + .HasColumnType("xml"); + } +} diff --git a/Dto/BookDto.cs b/Dto/BookDto.cs new file mode 100644 index 0000000..80c3879 --- /dev/null +++ b/Dto/BookDto.cs @@ -0,0 +1,14 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace example_csharp_app.Dto; + +public class BookDto +{ + // Для описания модели использовал fluent api в AppDbContext + public Guid Id { set; get; } + public string Title { set; get; } = string.Empty; + public string Author { set; get; } = string.Empty; + public DateTime YearPublished { set; get; } + public string BookIndex { set; get; } = string.Empty; +} diff --git a/ErrorLogMiddleware.cs b/ErrorLogMiddleware.cs new file mode 100644 index 0000000..142f045 --- /dev/null +++ b/ErrorLogMiddleware.cs @@ -0,0 +1,27 @@ +using System; + +namespace example_csharp_app; + +public class ErrorLogMiddleware +{ + private readonly RequestDelegate _next; + + public ErrorLogMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext httpContext, IConfiguration configuration) + { + try + { + await _next(httpContext); + return; + } + catch (Exception) + { + // todo: logme + httpContext.Response.StatusCode = 500; + } + } +} diff --git a/Filters/XmlAttributeFilter.cs b/Filters/XmlAttributeFilter.cs new file mode 100644 index 0000000..9252805 --- /dev/null +++ b/Filters/XmlAttributeFilter.cs @@ -0,0 +1,49 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Windows.Markup; +using System.Xml; + +namespace example_csharp_app.Filters; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] +public class XmlAttribute : ValidationAttribute +{ + + public XmlAttribute(): base("Passed string is not XML") + {} + + /// + /// Override of + /// + /// The value to test + /// + /// Returns if the is null or an empty string. + /// If then is returned for empty strings. + /// + public override bool IsValid(object? value) + { + if (value is null || value is not string) + { + return false; + } + + string possiblyXml = ((string)value).Trim(); + if (!possiblyXml.StartsWith('<') && !possiblyXml.EndsWith('>')) + { + return false; + } + XmlDocument xml = new XmlDocument(); + try + { + xml.LoadXml(possiblyXml); + } + catch (XmlException e) + { + // logme? + return false; + } + + return true; + } + +} diff --git a/Interfaces/IBookAggregator.cs b/Interfaces/IBookAggregator.cs new file mode 100644 index 0000000..01a7cde --- /dev/null +++ b/Interfaces/IBookAggregator.cs @@ -0,0 +1,14 @@ +using System; +using example_csharp_app.Models.Request; +using example_csharp_app.Models.Response; + +namespace example_csharp_app.Interfaces; + +public interface IBookAggregator +{ + Task GetBookListAsync(BookFilterRequest request); + Task GetBookAsync(Guid bookId); + Task UpdateBookAsync(BookUpdateRequest request); + Task AddBookAsync(BookCreateRequest request); + Task RemoveBookAsync(Guid bookId); +} diff --git a/Interfaces/IBookRepository.cs b/Interfaces/IBookRepository.cs new file mode 100644 index 0000000..54d2b7c --- /dev/null +++ b/Interfaces/IBookRepository.cs @@ -0,0 +1,14 @@ +using System; +using example_csharp_app.Dto; +using example_csharp_app.Models.Request; + +namespace example_csharp_app.Interfaces; + +public interface IBookRepository +{ + Task AddBookAsync(BookDto dto); + Task RemoveBookAsync(BookDto dto); + Task GetBookAsync(Guid bookId, bool enableEdit = true); + Task> GetBookListAsync(BookFilterRequest request); + Task UpdateBookAsync(BookDto dto); +} diff --git a/Interfaces/IBookService.cs b/Interfaces/IBookService.cs new file mode 100644 index 0000000..4ef2088 --- /dev/null +++ b/Interfaces/IBookService.cs @@ -0,0 +1,14 @@ +using System; +using example_csharp_app.Models.Model; +using example_csharp_app.Models.Request; + +namespace example_csharp_app.Interfaces; + +public interface IBookService +{ + Task> GetBooksAsync(BookFilterRequest request); + Task GetBookAsync(Guid bookId); + Task UpdateBookAsync(Guid bookId, BookUpdateModel model); + Task AddBookAsync(BookCreateModel model); + Task RemoveBookAsync(Guid bookId); +} diff --git a/Migrations/20260925040202_init.Designer.cs b/Migrations/20260925040202_init.Designer.cs new file mode 100644 index 0000000..5865dce --- /dev/null +++ b/Migrations/20260925040202_init.Designer.cs @@ -0,0 +1,56 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using example_app.Database.Connector; + +#nullable disable + +namespace example_csharp_app.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260925040202_init")] + partial class init + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("example_csharp_app.Dto.BookDto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("BookIndex") + .IsRequired() + .HasColumnType("xml"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("YearPublished") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Books", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Migrations/20260925040202_init.cs b/Migrations/20260925040202_init.cs new file mode 100644 index 0000000..c63589e --- /dev/null +++ b/Migrations/20260925040202_init.cs @@ -0,0 +1,37 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace example_csharp_app.Migrations +{ + /// + public partial class init : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Books", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Title = table.Column(type: "text", nullable: false), + Author = table.Column(type: "text", nullable: false), + YearPublished = table.Column(type: "timestamp with time zone", nullable: false), + BookIndex = table.Column(type: "xml", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Books", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Books"); + } + } +} diff --git a/Migrations/AppDbContextModelSnapshot.cs b/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..38e5f27 --- /dev/null +++ b/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,53 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using example_app.Database.Connector; + +#nullable disable + +namespace example_csharp_app.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.12") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("example_csharp_app.Dto.BookDto", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("BookIndex") + .IsRequired() + .HasColumnType("xml"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("YearPublished") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Books", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Models/Mapper/BookMapper.cs b/Models/Mapper/BookMapper.cs new file mode 100644 index 0000000..2ef77e3 --- /dev/null +++ b/Models/Mapper/BookMapper.cs @@ -0,0 +1,32 @@ +using System; +using example_csharp_app.Dto; +using example_csharp_app.Models.Model; + +namespace example_csharp_app.Models.Mapper; + +public static class BookMapper +{ + public static BookModel MapToModel(this BookDto dto) + { + return new BookModel() + { + Author = dto.Author, + BookIndex = dto.BookIndex, + Id = dto.Id, + Title = dto.Title, + YearPublished = dto.YearPublished + }; + } + + public static BookDto MapToDto(this BookCreateModel model) + { + return new BookDto() + { + Id = Guid.NewGuid(), + Author = model.Author, + BookIndex = model.BookIndex, + Title = model.Title, + YearPublished = model.YearPublished + }; + } +} diff --git a/Models/Mapper/RequestMapper.cs b/Models/Mapper/RequestMapper.cs new file mode 100644 index 0000000..f1bf607 --- /dev/null +++ b/Models/Mapper/RequestMapper.cs @@ -0,0 +1,30 @@ +using System; +using example_csharp_app.Models.Model; +using example_csharp_app.Models.Request; + +namespace example_csharp_app.Models.Mapper; + +public static class RequestMapper +{ + public static BookCreateModel MapToModel(this BookCreateRequest request) + { + return new BookCreateModel() + { + Author = request.Author, + BookIndex = request.BookIndex.Trim(), + Title = request.Title, + YearPublished = request.YearPublished + }; + } + + public static BookUpdateModel MapToModel(this BookUpdateRequest request) + { + return new BookUpdateModel() + { + Author = request.Author, + BookIndex = request.BookIndex.Trim(), + Title = request.Title, + YearPublished = request.YearPublished + }; + } +} diff --git a/Models/Model/BookCreateModel.cs b/Models/Model/BookCreateModel.cs new file mode 100644 index 0000000..6410a09 --- /dev/null +++ b/Models/Model/BookCreateModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace example_csharp_app.Models.Model; + +public class BookCreateModel +{ + public string Title { set; get; } = string.Empty; + public string Author { set; get; } = string.Empty; + public DateTime YearPublished { set; get; } + public string BookIndex { set; get; } = string.Empty; +} diff --git a/Models/Model/BookModel.cs b/Models/Model/BookModel.cs new file mode 100644 index 0000000..d04d8b0 --- /dev/null +++ b/Models/Model/BookModel.cs @@ -0,0 +1,12 @@ +using System; + +namespace example_csharp_app.Models.Model; + +public class BookModel +{ + public Guid Id { set; get; } + public string Title { set; get; } = string.Empty; + public string Author { set; get; } = string.Empty; + public DateTime YearPublished { set; get; } + public string BookIndex { set; get; } = string.Empty; +} diff --git a/Models/Model/BookUpdateModel.cs b/Models/Model/BookUpdateModel.cs new file mode 100644 index 0000000..1944bff --- /dev/null +++ b/Models/Model/BookUpdateModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace example_csharp_app.Models.Model; + +public class BookUpdateModel +{ + public string Title { set; get; } = string.Empty; + public string Author { set; get; } = string.Empty; + public DateTime YearPublished { set; get; } + public string BookIndex { set; get; } = string.Empty; +} diff --git a/Models/Request/BookCreateRequest.cs b/Models/Request/BookCreateRequest.cs new file mode 100644 index 0000000..d4d6ab3 --- /dev/null +++ b/Models/Request/BookCreateRequest.cs @@ -0,0 +1,17 @@ +using System; +using System.ComponentModel.DataAnnotations; +using example_csharp_app.Filters; + +namespace example_csharp_app.Models.Request; + +public class BookCreateRequest +{ + [Required] + public string Title { set; get; } = string.Empty; + [Required] + public string Author { set; get; } = string.Empty; + [Required] + public DateTime YearPublished { set; get; } + [Required, Xml] + public string BookIndex { set; get; } = string.Empty; +} diff --git a/Models/Request/BookFilterRequest.cs b/Models/Request/BookFilterRequest.cs new file mode 100644 index 0000000..aba4b91 --- /dev/null +++ b/Models/Request/BookFilterRequest.cs @@ -0,0 +1,10 @@ +using System; + +namespace example_csharp_app.Models.Request; + +public class BookFilterRequest +{ + public string? Author { set; get; } + public string? Index { set; get; } + public string? Title { set; get; } +} diff --git a/Models/Request/BookUpdateRequest.cs b/Models/Request/BookUpdateRequest.cs new file mode 100644 index 0000000..bdffc83 --- /dev/null +++ b/Models/Request/BookUpdateRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.ComponentModel.DataAnnotations; +using example_csharp_app.Filters; + +namespace example_csharp_app.Models.Request; + +public class BookUpdateRequest +{ + [Required] + public Guid Id { set; get; } = Guid.Empty; + [Required] + public string Title { set; get; } = string.Empty; + [Required] + public string Author { set; get; } = string.Empty; + [Required] + public DateTime YearPublished { set; get; } + [Required, Xml] + public string BookIndex { set; get; } = string.Empty; +} diff --git a/Models/Response/BookListResponse.cs b/Models/Response/BookListResponse.cs new file mode 100644 index 0000000..6dad92e --- /dev/null +++ b/Models/Response/BookListResponse.cs @@ -0,0 +1,9 @@ +using System; +using example_csharp_app.Models.Model; + +namespace example_csharp_app.Models.Response; + +public class BookListResponse +{ + public IReadOnlyCollection Books { set; get; } = []; +} diff --git a/Models/Response/BookOperationResponse.cs b/Models/Response/BookOperationResponse.cs new file mode 100644 index 0000000..7fe62ea --- /dev/null +++ b/Models/Response/BookOperationResponse.cs @@ -0,0 +1,8 @@ +using System; + +namespace example_csharp_app.Models.Response; + +public class BookOperationResponse +{ + public bool IsSuccess { set; get; } +} diff --git a/Models/Response/BookSingleResponse.cs b/Models/Response/BookSingleResponse.cs new file mode 100644 index 0000000..ecf5f3b --- /dev/null +++ b/Models/Response/BookSingleResponse.cs @@ -0,0 +1,9 @@ +using System; +using example_csharp_app.Models.Model; + +namespace example_csharp_app.Models.Response; + +public class BookSingleResponse +{ + public BookModel? Book { set; get; } +} diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..ee9a2c6 --- /dev/null +++ b/Program.cs @@ -0,0 +1,83 @@ + +using example_app.Database.Connector; +using example_csharp_app; +using example_csharp_app.Aggregators; +using example_csharp_app.Interfaces; +using example_csharp_app.Repositories; +using example_csharp_app.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + + +namespace Program +{ + public static class Program + { + private static void SetupDi(IServiceCollection services, IConfiguration configuration) + { + services.AddCors(); + + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddDbContext( + (provider, builder) => + { + //var settings = provider.GetRequiredService>(); // эта версия лучше + var connectionString = configuration.GetValue("ConnectionStrings:DefaultConnection"); + builder.UseNpgsql(connectionString); + }, + ServiceLifetime.Transient + ); + } + + public static int Main(string[] args) + { + try + { + var configurationBuilder = (new ConfigurationBuilder()) + .SetBasePath(Environment.CurrentDirectory) + .AddJsonFile("appsettings.json", false, true) + .AddJsonFile("appsettings.{environmentName}", true, true) + .AddEnvironmentVariables() + .AddCommandLine(args); + + var builder = WebApplication.CreateBuilder(args); + var configuration = configurationBuilder.Build(); + + builder.Services.AddControllers(); + builder.Services.AddSwaggerGen(); + + SetupDi(builder.Services, configuration); + + + var app = builder.Build(); + //if(app.Environment.IsDevelopment()) + //{ + app.UseSwagger(); + app.UseSwaggerUI(); + //} + app.MapControllers(); + app.UseMiddleware(); + //app.UseHttpsRedirection(); + + app.Run(); + + } catch (Exception) + { + // log + } + finally + { + // flush log + } + + return 0; + + } + } +} + + + diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..c862cad --- /dev/null +++ b/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:5222", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7189;http://localhost:5222", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/README.md b/README.md index ca9fe7e..41d9bfd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ -# example-csharp-app - +# example-csharp-app + diff --git a/Repositories/BookRepository.cs b/Repositories/BookRepository.cs new file mode 100644 index 0000000..cbd9fe7 --- /dev/null +++ b/Repositories/BookRepository.cs @@ -0,0 +1,70 @@ +using System; +using example_app.Database.Connector; +using example_csharp_app.Dto; +using example_csharp_app.Interfaces; +using Microsoft.EntityFrameworkCore; +using System.Linq; +using example_csharp_app.Models.Request; + +namespace example_csharp_app.Repositories; + +public class BookRepository : IBookRepository +{ + private readonly AppDbContext _dbContext; + + public BookRepository(AppDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task AddBookAsync(BookDto dto) + { + _dbContext.Add(dto); + return await _dbContext.SaveChangesAsync(); + } + + public async Task GetBookAsync(Guid bookId, bool enableEdit = true) + { + var query = _dbContext.Books.Where(x => x.Id == bookId); + if(enableEdit == false) + { + query = query.AsNoTracking(); + } + return await query.FirstOrDefaultAsync(); + } + + public async Task> GetBookListAsync(BookFilterRequest request) + { + IQueryable query = _dbContext.Books.AsNoTracking(); + if(request.Author != null) + { + query = query.Where(x => x.Author.Contains(request.Author)); + } + if(request.Title != null) + { + query = query.Where(x => x.Title.Contains(request.Title)); + } + if(request.Index != null) + { + var interminentQuery = await query.ToListAsync(); + return interminentQuery.Where(x => x.BookIndex.Contains(request.Index)).ToList(); // ничего лучше не придумал + // xml ~~ xml - operation does not exist + //query = query.Where(x => x.BookIndex.Contains(request.Index)); + } + + return await query.ToListAsync(); // можно возвращать query как IEnumerable, но в некоторых случаях это будет приводить к исключению + // например если контекст инжектится фабрикой + } + + public async Task RemoveBookAsync(BookDto dto) + { + _dbContext.Books.Remove(dto); + return await _dbContext.SaveChangesAsync(); + } + + public async Task UpdateBookAsync(BookDto dto) + { + _dbContext.Books.Update(dto); + return await _dbContext.SaveChangesAsync(); + } +} diff --git a/Services/BookService.cs b/Services/BookService.cs new file mode 100644 index 0000000..51b5269 --- /dev/null +++ b/Services/BookService.cs @@ -0,0 +1,61 @@ +using System; +using example_csharp_app.Dto; +using example_csharp_app.Interfaces; +using example_csharp_app.Models.Mapper; +using example_csharp_app.Models.Model; +using example_csharp_app.Models.Request; + +namespace example_csharp_app.Services; + +public class BookService : IBookService +{ + private readonly IBookRepository _bookRepository; + + public BookService(IBookRepository bookRepository) + { + _bookRepository = bookRepository; + } + + public async Task AddBookAsync(BookCreateModel model) + { + var dto = model.MapToDto(); + var result = await _bookRepository.AddBookAsync(dto); + return result; + } + + public async Task GetBookAsync(Guid bookId) + { + var result = await _bookRepository.GetBookAsync(bookId, enableEdit: false); + return result?.MapToModel(); + } + + public async Task> GetBooksAsync(BookFilterRequest request) + { + var result = await _bookRepository.GetBookListAsync(request); + return result.Select(x => x.MapToModel()).ToList(); + } + + public async Task RemoveBookAsync(Guid bookId) + { + var book = await GetBookForEdit(bookId); + return await _bookRepository.RemoveBookAsync(book); + } + + public async Task UpdateBookAsync(Guid bookId, BookUpdateModel model) + { + var book = await GetBookForEdit(bookId); + + book.Author = model.Author; + book.BookIndex = model.BookIndex; + book.Title = model.Title; + book.YearPublished = model.YearPublished; + + return await _bookRepository.UpdateBookAsync(book); + } + + private async Task GetBookForEdit(Guid bookId) + { + var book = await _bookRepository.GetBookAsync(bookId); + return book ?? throw new ArgumentException(); + } +} \ No newline at end of file diff --git a/example-csharp-app.csproj b/example-csharp-app.csproj new file mode 100644 index 0000000..f5b99c6 --- /dev/null +++ b/example-csharp-app.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + example_csharp_app + + + + + + + + + + + + + + + + \ No newline at end of file