Try to map everything beforehand, bad decision, checkpoint

This commit is contained in:
lumijiez
2025-05-04 01:22:10 +03:00
parent 65f25318a5
commit 8f565ff0af
10 changed files with 306 additions and 1 deletions

View File

@@ -0,0 +1,35 @@
using System.Diagnostics;
using MediatR;
using Microsoft.Extensions.Logging;
namespace Printbase.Application.Common.Behaviors;
public class LoggingBehavior<TRequest, TResponse>(ILogger<LoggingBehavior<TRequest, TResponse>> logger)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
logger.LogInformation($"Handling {requestName}");
var stopwatch = Stopwatch.StartNew();
try
{
var response = await next(cancellationToken);
stopwatch.Stop();
logger.LogInformation($"Handling {requestName} in {stopwatch.ElapsedMilliseconds}ms");
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
logger.LogError(ex, $"Error handling {requestName} after {stopwatch.ElapsedMilliseconds}ms");
throw;
}
}
}

View File

@@ -0,0 +1,31 @@
using FluentValidation;
using MediatR;
using ValidationException = System.ComponentModel.DataAnnotations.ValidationException;
namespace Printbase.Application.Common.Behaviors;
public class ValidationBehavior<TRequest, TResponse>(IEnumerable<IValidator<TRequest>> validators)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (!validators.Any())
return await next(cancellationToken);
var context = new ValidationContext<TRequest>(request);
var validationResults = await Task.WhenAll(
validators.Select(v => v.ValidateAsync(context, cancellationToken)));
var failures = validationResults
.SelectMany(r => r.Errors)
.Where(f => f != null)
.ToList();
if (failures.Count != 0)
throw new ValidationException(failures.ToString());
return await next(cancellationToken);
}
}

View File

@@ -0,0 +1,72 @@
using AutoMapper;
using Printbase.Application.ProductGroup;
using Printbase.Application.Products;
using Printbase.Application.ProductType;
using Printbase.Domain.Entities.Products;
namespace Printbase.Application.Common.Mappings;
public class ApplicationMappingProfile : Profile
{
public ApplicationMappingProfile()
{
CreateMap<Product, ProductDto>()
.ForMember(dest => dest.TypeName, opt => opt.Ignore())
.ForMember(dest => dest.Variants, opt => opt.MapFrom(src => src.Variants));
CreateMap<ProductCreateDto, Product>()
.ConstructUsing((src, ctx) => new Product(
Guid.NewGuid(),
src.Name,
src.TypeId,
src.Description,
src.Discount))
.ForMember(dest => dest.Variants, opt => opt.Ignore());
CreateMap<ProductUpdateDto, Product>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.Variants, opt => opt.Ignore());
CreateMap<ProductVariant, ProductVariantDto>()
.ForMember(dest => dest.EffectivePrice, opt => opt.MapFrom(src => src.GetEffectivePrice(src.Discount)));
CreateMap<ProductVariantCreateDto, ProductVariant>()
.ConstructUsing((src, ctx) => new ProductVariant(
Guid.NewGuid(),
Guid.Empty,
src.Price,
src.Color,
src.Size,
src.Discount,
src.Stock));
CreateMap<ProductVariantUpdateDto, ProductVariant>()
.ForMember(dest => dest.ProductId, opt => opt.Ignore());
CreateMap<Domain.Entities.Products.ProductType, ProductTypeDto>()
.ForMember(dest => dest.GroupName, opt => opt.Ignore());
CreateMap<ProductTypeCreateDto, Domain.Entities.Products.ProductType>()
.ConstructUsing((src, ctx) => new Domain.Entities.Products.ProductType(
Guid.NewGuid(),
src.Name,
src.GroupId,
src.Description));
CreateMap<ProductTypeUpdateDto, Domain.Entities.Products.ProductType>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.GroupId, opt => opt.Ignore());
CreateMap<Domain.Entities.Products.ProductGroup, ProductGroupDto>();
CreateMap<ProductGroupCreateDto, Domain.Entities.Products.ProductGroup>()
.ConstructUsing((src, ctx) => new Domain.Entities.Products.ProductGroup(
Guid.NewGuid(),
src.Name,
src.Description));
CreateMap<ProductGroupUpdateDto, Domain.Entities.Products.ProductGroup>()
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.Types, opt => opt.Ignore());
}
}

View File

@@ -0,0 +1,43 @@
using System.Net;
namespace Printbase.Application.Common.Models;
public class ApiResponse<T>
{
public bool Succeeded { get; protected set; }
public string Message { get; protected set; } = string.Empty;
public T? Data { get; protected set; }
public IList<string> Errors { get; protected set; } = new List<string>();
public HttpStatusCode StatusCode { get; protected set; }
public static ApiResponse<T> Success(T data, HttpStatusCode statusCode = HttpStatusCode.OK, string message = "")
{
return new ApiResponse<T>
{
Succeeded = true,
Data = data,
StatusCode = statusCode,
Message = message
};
}
public static ApiResponse<T> Fail(string error, HttpStatusCode statusCode = HttpStatusCode.BadRequest)
{
return new ApiResponse<T>
{
Succeeded = false,
Errors = new List<string> { error },
StatusCode = statusCode
};
}
public static ApiResponse<T> Fail(IList<string> errors, HttpStatusCode statusCode = HttpStatusCode.BadRequest)
{
return new ApiResponse<T>
{
Succeeded = false,
Errors = errors,
StatusCode = statusCode
};
}
}

View File

@@ -8,7 +8,17 @@
<ItemGroup> <ItemGroup>
<Folder Include="Abstractions\" /> <Folder Include="Abstractions\" />
<Folder Include="Common\" /> </ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="FluentValidation" Version="12.0.0-preview1" />
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Printbase.Domain\Printbase.Domain.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,23 @@
using Printbase.Application.ProductType;
namespace Printbase.Application.ProductGroup;
public class ProductGroupDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public List<ProductTypeDto> Types { get; set; } = [];
}
public class ProductGroupCreateDto
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
}
public class ProductGroupUpdateDto
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
}

View File

@@ -0,0 +1,23 @@
namespace Printbase.Application.ProductType;
public class ProductTypeDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Guid GroupId { get; set; }
public string GroupName { get; set; } = string.Empty;
}
public class ProductTypeCreateDto
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public Guid GroupId { get; set; }
}
public class ProductTypeUpdateDto
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
}

View File

@@ -0,0 +1,60 @@
namespace Printbase.Application.Products;
public class ProductDto
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public decimal? Discount { get; set; }
public Guid TypeId { get; set; }
public string TypeName { get; set; } = string.Empty;
public List<ProductVariantDto> Variants { get; set; } = new();
}
public class ProductVariantDto
{
public Guid Id { get; set; }
public Guid ProductId { get; set; }
public string? Color { get; set; }
public string? Size { get; set; }
public decimal Price { get; set; }
public decimal? Discount { get; set; }
public int Stock { get; set; }
public decimal EffectivePrice { get; set; }
}
public class ProductCreateDto
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public decimal? Discount { get; set; }
public Guid TypeId { get; set; }
public List<ProductVariantCreateDto> Variants { get; set; } = new();
}
public class ProductVariantCreateDto
{
public string? Color { get; set; }
public string? Size { get; set; }
public decimal Price { get; set; }
public decimal? Discount { get; set; }
public int Stock { get; set; }
}
public class ProductUpdateDto
{
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public decimal? Discount { get; set; }
public Guid TypeId { get; set; }
}
public class ProductVariantUpdateDto
{
public Guid Id { get; set; }
public string? Color { get; set; }
public string? Size { get; set; }
public decimal Price { get; set; }
public decimal? Discount { get; set; }
public int Stock { get; set; }
}

View File

@@ -11,6 +11,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Printbase.Application\Printbase.Application.csproj" />
<ProjectReference Include="..\Printbase.Infrastructure\Printbase.Infrastructure.csproj" /> <ProjectReference Include="..\Printbase.Infrastructure\Printbase.Infrastructure.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,7 @@
using System.Reflection;
using MediatR;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Printbase.Application.Common.Behaviors;
using Printbase.Domain.Repositories; using Printbase.Domain.Repositories;
using Printbase.Infrastructure.Database; using Printbase.Infrastructure.Database;
using Printbase.Infrastructure.Mapping; using Printbase.Infrastructure.Mapping;
@@ -15,6 +18,10 @@ services.AddDbContext<ApplicationDbContext>(options =>
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddAutoMapper(typeof(ProductMappingProfile).Assembly); services.AddAutoMapper(typeof(ProductMappingProfile).Assembly);
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddScoped<IProductRepository, ProductRepository>(); services.AddScoped<IProductRepository, ProductRepository>();
services.AddScoped<IProductVariantRepository, ProductVariantRepository>(); services.AddScoped<IProductVariantRepository, ProductVariantRepository>();
services.AddScoped<IProductTypeRepository, ProductTypeRepository>(); services.AddScoped<IProductTypeRepository, ProductTypeRepository>();