diff --git a/src/Supermarket.API/Controllers/CategoriesController.cs b/src/Supermarket.API/Controllers/CategoriesController.cs index a21bf8d..418f6ea 100644 --- a/src/Supermarket.API/Controllers/CategoriesController.cs +++ b/src/Supermarket.API/Controllers/CategoriesController.cs @@ -5,26 +5,20 @@ namespace Supermarket.API.Controllers { - public class CategoriesController : BaseApiController + public class CategoriesController(IServiceManager serviceManager, IMapper mapper) : BaseApiController { - private readonly ICategoryService _categoryService; - private readonly IMapper _mapper; + private readonly IServiceManager _serviceManager = serviceManager; + private readonly IMapper _mapper = mapper; - public CategoriesController(ICategoryService categoryService, IMapper mapper) - { - _categoryService = categoryService; - _mapper = mapper; - } - - /// - /// Lists all categories. - /// - /// List os categories. - [HttpGet] + /// + /// Lists all categories. + /// + /// List os categories. + [HttpGet] [ProducesResponseType(typeof(IEnumerable), 200)] public async Task> ListAsync() { - var categories = await _categoryService.ListAsync(); + var categories = await _serviceManager.CategoryService.ListAsync(); return _mapper.Map>(categories); } @@ -39,7 +33,7 @@ public async Task> ListAsync() public async Task PostAsync([FromBody] SaveCategoryResource resource) { var category = _mapper.Map(resource); - var result = await _categoryService.SaveAsync(category); + var result = await _serviceManager.CategoryService.SaveAsync(category); if (!result.Success) { @@ -62,7 +56,7 @@ public async Task PostAsync([FromBody] SaveCategoryResource resou public async Task PutAsync(int id, [FromBody] SaveCategoryResource resource) { var category = _mapper.Map(resource); - var result = await _categoryService.UpdateAsync(id, category); + var result = await _serviceManager.CategoryService.UpdateAsync(id, category); if (!result.Success) { @@ -83,7 +77,7 @@ public async Task PutAsync(int id, [FromBody] SaveCategoryResourc [ProducesResponseType(typeof(ErrorResource), 400)] public async Task DeleteAsync(int id) { - var result = await _categoryService.DeleteAsync(id); + var result = await _serviceManager.CategoryService.DeleteAsync(id); if (!result.Success) { diff --git a/src/Supermarket.API/Controllers/ProductsController.cs b/src/Supermarket.API/Controllers/ProductsController.cs index ed45787..2df06bd 100644 --- a/src/Supermarket.API/Controllers/ProductsController.cs +++ b/src/Supermarket.API/Controllers/ProductsController.cs @@ -5,16 +5,10 @@ namespace Supermarket.API.Controllers { - public class ProductsController : BaseApiController + public class ProductsController(IServiceManager serviceManager, IMapper mapper) : BaseApiController { - private readonly IProductService _productService; - private readonly IMapper _mapper; - - public ProductsController(IProductService productService, IMapper mapper) - { - _productService = productService; - _mapper = mapper; - } + private readonly IServiceManager _serviceManager = serviceManager; + private readonly IMapper _mapper = mapper; /// /// Lists all existing products according to query filters. @@ -25,7 +19,7 @@ public ProductsController(IProductService productService, IMapper mapper) public async Task> ListAsync([FromQuery] ProductsQueryResource query) { var productsQuery = _mapper.Map(query); - var queryResult = await _productService.ListAsync(productsQuery); + var queryResult = await _serviceManager.ProductService.ListAsync(productsQuery); return _mapper.Map>(queryResult); } @@ -41,7 +35,7 @@ public async Task> ListAsync([FromQuery] Pr public async Task PostAsync([FromBody] SaveProductResource resource) { var product = _mapper.Map(resource); - var result = await _productService.SaveAsync(product); + var result = await _serviceManager.ProductService.SaveAsync(product); if (!result.Success) { @@ -64,7 +58,7 @@ public async Task PostAsync([FromBody] SaveProductResource resour public async Task PutAsync(int id, [FromBody] SaveProductResource resource) { var product = _mapper.Map(resource); - var result = await _productService.UpdateAsync(id, product); + var result = await _serviceManager.ProductService.UpdateAsync(id, product); if (!result.Success) { @@ -85,7 +79,7 @@ public async Task PutAsync(int id, [FromBody] SaveProductResource [ProducesResponseType(typeof(ErrorResource), 400)] public async Task DeleteAsync(int id) { - var result = await _productService.DeleteAsync(id); + var result = await _serviceManager.ProductService.DeleteAsync(id); if (!result.Success) { diff --git a/src/Supermarket.API/Domain/Repositories/IUnitOfWork.cs b/src/Supermarket.API/Domain/Repositories/IUnitOfWork.cs index 98706ad..b7b08d3 100644 --- a/src/Supermarket.API/Domain/Repositories/IUnitOfWork.cs +++ b/src/Supermarket.API/Domain/Repositories/IUnitOfWork.cs @@ -2,6 +2,8 @@ namespace Supermarket.API.Domain.Repositories { public interface IUnitOfWork { + IProductRepository ProductRepository { get; } + ICategoryRepository CategoryRepository { get; } Task CompleteAsync(); } } \ No newline at end of file diff --git a/src/Supermarket.API/Domain/Services/IServiceManager.cs b/src/Supermarket.API/Domain/Services/IServiceManager.cs new file mode 100644 index 0000000..a72e1d4 --- /dev/null +++ b/src/Supermarket.API/Domain/Services/IServiceManager.cs @@ -0,0 +1,8 @@ +namespace Supermarket.API.Domain.Services +{ + public interface IServiceManager + { + IProductService ProductService { get; } + ICategoryService CategoryService { get; } + } +} diff --git a/src/Supermarket.API/Persistence/Repositories/BaseRepository.cs b/src/Supermarket.API/Persistence/Repositories/BaseRepository.cs index daf5b3a..dfe256e 100644 --- a/src/Supermarket.API/Persistence/Repositories/BaseRepository.cs +++ b/src/Supermarket.API/Persistence/Repositories/BaseRepository.cs @@ -2,13 +2,8 @@ namespace Supermarket.API.Persistence.Repositories { - public abstract class BaseRepository + public abstract class BaseRepository(AppDbContext context) { - protected readonly AppDbContext _context; - - public BaseRepository(AppDbContext context) - { - _context = context; - } + protected readonly AppDbContext _context = context; } } \ No newline at end of file diff --git a/src/Supermarket.API/Persistence/Repositories/UnitOfWork.cs b/src/Supermarket.API/Persistence/Repositories/UnitOfWork.cs index b7aa8c0..f9990de 100644 --- a/src/Supermarket.API/Persistence/Repositories/UnitOfWork.cs +++ b/src/Supermarket.API/Persistence/Repositories/UnitOfWork.cs @@ -6,10 +6,13 @@ namespace Supermarket.API.Persistence.Repositories public class UnitOfWork(AppDbContext context) : IUnitOfWork { private readonly AppDbContext _context = context; + private readonly Lazy _productRepository= new Lazy(() => new ProductRepository(context)); + private readonly Lazy _categoryRepository= new Lazy(() => new CategoryRepository(context)); - public async Task CompleteAsync() - { - await _context.SaveChangesAsync(); - } + + public IProductRepository ProductRepository => _productRepository.Value; + public ICategoryRepository CategoryRepository => _categoryRepository.Value; + + public async Task CompleteAsync() => await _context.SaveChangesAsync(); } } \ No newline at end of file diff --git a/src/Supermarket.API/Properties/launchSettings.json b/src/Supermarket.API/Properties/launchSettings.json index ce03645..b0cb87d 100644 --- a/src/Supermarket.API/Properties/launchSettings.json +++ b/src/Supermarket.API/Properties/launchSettings.json @@ -29,5 +29,13 @@ "httpPort": 5000 } }, - "$schema": "http://json.schemastore.org/launchsettings.json" + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:62953/", + "sslPort": 44321 + } + } } \ No newline at end of file diff --git a/src/Supermarket.API/Resources/ErrorResource.cs b/src/Supermarket.API/Resources/ErrorResource.cs index 70cc737..90d7ac8 100644 --- a/src/Supermarket.API/Resources/ErrorResource.cs +++ b/src/Supermarket.API/Resources/ErrorResource.cs @@ -5,10 +5,7 @@ public record ErrorResource public bool Success => false; public List Messages { get; private set; } - public ErrorResource(List messages) - { - Messages = messages ?? []; - } + public ErrorResource(List messages) => Messages = messages ?? []; public ErrorResource(string message) { diff --git a/src/Supermarket.API/Services/CategoryService.cs b/src/Supermarket.API/Services/CategoryService.cs index 90126a1..77303c8 100644 --- a/src/Supermarket.API/Services/CategoryService.cs +++ b/src/Supermarket.API/Services/CategoryService.cs @@ -6,35 +6,24 @@ namespace Supermarket.API.Services { - public class CategoryService : ICategoryService + public class CategoryService( + IUnitOfWork unitOfWork, + IMemoryCache cache, + ILogger logger + ) : ICategoryService { - private readonly ICategoryRepository _categoryRepository; - private readonly IUnitOfWork _unitOfWork; - private readonly IMemoryCache _cache; - private readonly ILogger _logger; + private readonly IUnitOfWork _unitOfWork = unitOfWork; + private readonly IMemoryCache _cache = cache; + private readonly ILogger _logger = logger; - public CategoryService - ( - ICategoryRepository categoryRepository, - IUnitOfWork unitOfWork, - IMemoryCache cache, - ILogger logger - ) - { - _categoryRepository = categoryRepository; - _unitOfWork = unitOfWork; - _cache = cache; - _logger = logger; - } - - public async Task> ListAsync() + public async Task> ListAsync() { // Here I try to get the categories list from the memory cache. If there is no data in cache, the anonymous method will be // called, setting the cache to expire one minute ahead and returning the Task that lists the categories from the repository. var categories = await _cache.GetOrCreateAsync(CacheKeys.CategoriesList, (entry) => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); - return _categoryRepository.ListAsync(); + return _unitOfWork.CategoryRepository.ListAsync(); }); return categories ?? new List(); @@ -44,7 +33,7 @@ public async Task> SaveAsync(Category category) { try { - await _categoryRepository.AddAsync(category); + await _unitOfWork.CategoryRepository.AddAsync(category); await _unitOfWork.CompleteAsync(); return new Response(category); @@ -58,7 +47,7 @@ public async Task> SaveAsync(Category category) public async Task> UpdateAsync(int id, Category category) { - var existingCategory = await _categoryRepository.FindByIdAsync(id); + var existingCategory = await _unitOfWork.CategoryRepository.FindByIdAsync(id); if (existingCategory == null) { return new Response("Category not found."); @@ -80,7 +69,7 @@ public async Task> UpdateAsync(int id, Category category) public async Task> DeleteAsync(int id) { - var existingCategory = await _categoryRepository.FindByIdAsync(id); + var existingCategory = await _unitOfWork.CategoryRepository.FindByIdAsync(id); if (existingCategory == null) { return new Response("Category not found."); @@ -88,7 +77,7 @@ public async Task> DeleteAsync(int id) try { - _categoryRepository.Remove(existingCategory); + _unitOfWork.CategoryRepository.Remove(existingCategory); await _unitOfWork.CompleteAsync(); return new Response(existingCategory); diff --git a/src/Supermarket.API/Services/ProductService.cs b/src/Supermarket.API/Services/ProductService.cs index 3d02734..55232f6 100644 --- a/src/Supermarket.API/Services/ProductService.cs +++ b/src/Supermarket.API/Services/ProductService.cs @@ -6,29 +6,16 @@ namespace Supermarket.API.Services { - public class ProductService : IProductService + public class ProductService( + IUnitOfWork unitOfWork, + IMemoryCache cache, + ILogger logger + ) : IProductService { - private readonly IProductRepository _productRepository; - private readonly ICategoryRepository _categoryRepository; - private readonly IUnitOfWork _unitOfWork; - private readonly IMemoryCache _cache; - private readonly ILogger _logger; - - public ProductService - ( - IProductRepository productRepository, - ICategoryRepository categoryRepository, - IUnitOfWork unitOfWork, - IMemoryCache cache, - ILogger logger - ) - { - _productRepository = productRepository; - _categoryRepository = categoryRepository; - _unitOfWork = unitOfWork; - _cache = cache; - _logger = logger; - } + + private readonly IUnitOfWork _unitOfWork = unitOfWork; + private readonly IMemoryCache _cache = cache; + private readonly ILogger _logger = logger; public async Task> ListAsync(ProductsQuery query) { @@ -39,7 +26,7 @@ public async Task> ListAsync(ProductsQuery query) var products = await _cache.GetOrCreateAsync(cacheKey, (entry) => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1); - return _productRepository.ListAsync(query); + return _unitOfWork.ProductRepository.ListAsync(query); }); return products!; @@ -54,11 +41,11 @@ public async Task> SaveAsync(Product product) You can create a method into the CategoryService class to return the category and inject the service here if you prefer, but it doesn't matter given the API scope. */ - var existingCategory = await _categoryRepository.FindByIdAsync(product.CategoryId); + var existingCategory = await _unitOfWork.CategoryRepository.FindByIdAsync(product.CategoryId); if (existingCategory == null) return new Response("Invalid category."); - await _productRepository.AddAsync(product); + await _unitOfWork.ProductRepository.AddAsync(product); await _unitOfWork.CompleteAsync(); return new Response(product); @@ -72,12 +59,12 @@ it doesn't matter given the API scope. public async Task> UpdateAsync(int id, Product product) { - var existingProduct = await _productRepository.FindByIdAsync(id); + var existingProduct = await _unitOfWork.ProductRepository.FindByIdAsync(id); if (existingProduct == null) return new Response("Product not found."); - var existingCategory = await _categoryRepository.FindByIdAsync(product.CategoryId); + var existingCategory = await _unitOfWork.CategoryRepository.FindByIdAsync(product.CategoryId); if (existingCategory == null) return new Response("Invalid category."); @@ -88,7 +75,7 @@ public async Task> UpdateAsync(int id, Product product) try { - _productRepository.Update(existingProduct); + _unitOfWork.ProductRepository.Update(existingProduct); await _unitOfWork.CompleteAsync(); return new Response(existingProduct); @@ -102,14 +89,14 @@ public async Task> UpdateAsync(int id, Product product) public async Task> DeleteAsync(int id) { - var existingProduct = await _productRepository.FindByIdAsync(id); + var existingProduct = await _unitOfWork.ProductRepository.FindByIdAsync(id); if (existingProduct == null) return new Response("Product not found."); try { - _productRepository.Remove(existingProduct); + _unitOfWork.ProductRepository.Remove(existingProduct); await _unitOfWork.CompleteAsync(); return new Response(existingProduct); diff --git a/src/Supermarket.API/Services/ServiceManager.cs b/src/Supermarket.API/Services/ServiceManager.cs new file mode 100644 index 0000000..37b7989 --- /dev/null +++ b/src/Supermarket.API/Services/ServiceManager.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Caching.Memory; +using Supermarket.API.Domain.Repositories; +using Supermarket.API.Domain.Services; + +namespace Supermarket.API.Services +{ + public class ServiceManager( + IUnitOfWork unitOfWork, + IMemoryCache cache, + ILogger loggerProduct, + ILogger loggerCategory) : IServiceManager + { + private readonly IUnitOfWork _unitOfWork= unitOfWork; + private readonly IMemoryCache _cache= cache; + private readonly ILogger _logger=loggerProduct; + private readonly ILogger _loggerCategory=loggerCategory; + + private readonly Lazy _productService = new Lazy(() => new ProductService(unitOfWork, cache, loggerProduct)); + private readonly Lazy _categoryService= new Lazy(() => new CategoryService(unitOfWork, cache,loggerCategory)); + + public IProductService ProductService => _productService.Value; + + public ICategoryService CategoryService => _categoryService.Value; + } +} diff --git a/src/Supermarket.API/Startup.cs b/src/Supermarket.API/Startup.cs index 4848a62..9d87edf 100644 --- a/src/Supermarket.API/Startup.cs +++ b/src/Supermarket.API/Startup.cs @@ -14,16 +14,16 @@ public class Startup private readonly IConfiguration Configuration; public Startup(IConfiguration configuration) => Configuration = configuration; - + public void ConfigureServices(IServiceCollection services) { services.AddMemoryCache(); services.AddCustomSwagger(); - services.Configure(options => options.LowercaseUrls = true); + services.Configure(options => options.LowercaseUrls = true); - services.AddControllers().ConfigureApiBehaviorOptions(options => + services.AddControllers().ConfigureApiBehaviorOptions(options => { // Adds a custom error response factory when ModelState is invalid options.InvalidModelStateResponseFactory = InvalidModelStateResponseFactory.ProduceErrorResponse; @@ -34,12 +34,10 @@ public void ConfigureServices(IServiceCollection services) options.UseInMemoryDatabase(Configuration.GetConnectionString("memory") ?? "data-in-memory"); }); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddAutoMapper(typeof(Startup)); }