This commit is contained in:
Lavardin 2022-05-10 21:45:11 -07:00
commit 263aa50631
20 changed files with 473 additions and 12 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
obj obj
bin bin
appsettings.json appsettings.json
*.db *.db*

View File

@ -6,6 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="11.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@ -1,6 +1,9 @@
using Core.Entities; using Core.Entities;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Core.Interfaces; using Core.Interfaces;
using Core.Specifications;
using API.Dtos;
using AutoMapper;
namespace API.Controllers namespace API.Controllers
{ {
@ -8,24 +11,46 @@ namespace API.Controllers
[Route("api/[controller]")] [Route("api/[controller]")]
public class ProductsController : ControllerBase public class ProductsController : ControllerBase
{ {
private readonly iProductRepository _repo; private readonly IGenericRepository<Product> _productsRepo;
public ProductsController(iProductRepository repo) private readonly IGenericRepository<ProductBrand> _productBrandRepo;
private readonly IGenericRepository<ProductType> _productTypeRepo;
private readonly IMapper _mapper;
public ProductsController(IGenericRepository<Product> productsRepo, IGenericRepository<ProductBrand> productBrandRepo, IGenericRepository<ProductType> productTypeRepo, IMapper mapper)
{ {
_repo = repo; _mapper = mapper;
_productTypeRepo = productTypeRepo;
_productBrandRepo = productBrandRepo;
_productsRepo = productsRepo;
} }
[HttpGet] [HttpGet]
public async Task<ActionResult<List<Product>>> GetProducts() public async Task<ActionResult<IReadOnlyList<ProductToReturnDto>>> GetProducts()
{ {
var products = await _repo.GetProductsAync(); var spec = new ProductsWithTypesAndBrandsSpecification();
var products = await _productsRepo.ListAsync(spec);
return Ok(products); return Ok(_mapper.Map<IReadOnlyList<Product>, IReadOnlyList<ProductToReturnDto>>(products));
} }
[HttpGet("{id}")] [HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id) public async Task<ActionResult<ProductToReturnDto>> GetProduct(int id)
{ {
return await _repo.GetProductByIdAsync(id); var spec = new ProductsWithTypesAndBrandsSpecification(id);
var product = await _productsRepo.GetEntityWithSpec(spec);
return _mapper.Map<Product, ProductToReturnDto>(product);
}
[HttpGet("brands")]
public async Task<ActionResult<IReadOnlyList<ProductBrand>>> GetProductBrands()
{
return Ok(await _productBrandRepo.ListAllAsync());
}
[HttpGet("types")]
public async Task<ActionResult<IReadOnlyList<ProductType>>> GetProductTypes()
{
return Ok(await _productTypeRepo.ListAllAsync());
} }
} }
} }

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace API.Dtos
{
public class ProductToReturnDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string PictureUrl { get; set; }
public string ProductType { get; set; }
public string ProductBrand { get; set; }
}
}

View File

@ -0,0 +1,16 @@
using API.Dtos;
using AutoMapper;
using Core.Entities;
namespace API.Helpers
{
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<Product, ProductToReturnDto>()
.ForMember(d => d.ProductBrand, o => o.MapFrom(s => s.ProductBrand.Name))
.ForMember(d => d.ProductType, o => o.MapFrom(s => s.ProductType.Name));
}
}
}

View File

@ -1,3 +1,4 @@
using Infrastructure;
using Infrastructure.Data; using Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -16,6 +17,7 @@ namespace API
{ {
var context = services.GetRequiredService<StoreContext>(); var context = services.GetRequiredService<StoreContext>();
await context.Database.MigrateAsync(); await context.Database.MigrateAsync();
await StoreContextSeed.SeedAsync(context, loggerFactory);
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -1,3 +1,4 @@
using API.Helpers;
using Core.Interfaces; using Core.Interfaces;
using Infrastructure.Data; using Infrastructure.Data;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
@ -21,6 +22,8 @@ namespace API
services.AddControllers(); services.AddControllers();
services.AddDbContext<StoreContext>(x => x.UseSqlite(_config.GetConnectionString("DefaultConnection"))); services.AddDbContext<StoreContext>(x => x.UseSqlite(_config.GetConnectionString("DefaultConnection")));
services.AddScoped<iProductRepository, ProductRepository>(); services.AddScoped<iProductRepository, ProductRepository>();
services.AddScoped(typeof(IGenericRepository<>), (typeof(GenericRepository<>)));
services.AddAutoMapper(typeof(MappingProfiles));
services.AddSwaggerGen(c => services.AddSwaggerGen(c =>
{ {
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIv5", Version = "v1" }); c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebAPIv5", Version = "v1" });

Binary file not shown.

View File

@ -0,0 +1,13 @@
using Core.Entities;
using Core.Specifications;
namespace Core.Interfaces
{
public interface IGenericRepository<T> where T : BaseEntity
{
Task<T> GetByIdAsync(int id);
Task<IReadOnlyList<T>> ListAllAsync();
Task<T> GetEntityWithSpec(ISpecification<T> spec);
Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec);
}
}

View File

@ -6,5 +6,7 @@ namespace Core.Interfaces
{ {
Task<Product> GetProductByIdAsync(int id); Task<Product> GetProductByIdAsync(int id);
Task<IReadOnlyList<Product>> GetProductsAync(); Task<IReadOnlyList<Product>> GetProductsAync();
Task<IReadOnlyList<ProductBrand>> GetProductBrandsAsync();
Task<IReadOnlyList<ProductType>> GetProductTypesAsync();
} }
} }

View File

@ -0,0 +1,23 @@
using System.Linq.Expressions;
namespace Core.Specifications
{
public class BaseSpecification<T> : ISpecification<T>
{
public BaseSpecification()
{
}
public BaseSpecification(Expression<Func<T, bool>> criteria)
{
Criteria = criteria;
}
public Expression<Func<T, bool>> Criteria { get; }
public List<Expression<Func<T, object>>> Includes { get; } =
new List<Expression<Func<T, object>>>();
protected void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
}
}
}

View File

@ -0,0 +1,11 @@
using System.Linq.Expressions;
namespace Core.Specifications
{
public interface ISpecification<T>
{
Expression<Func<T, bool>> Criteria {get; }
List<Expression<Func<T, object>>> Includes {get; }
}
}

View File

@ -0,0 +1,20 @@
using System.Linq.Expressions;
using Core.Entities;
namespace Core.Specifications
{
public class ProductsWithTypesAndBrandsSpecification : BaseSpecification<Product>
{
public ProductsWithTypesAndBrandsSpecification()
{
AddInclude(x => x.ProductType);
AddInclude(x => x.ProductBrand);
}
public ProductsWithTypesAndBrandsSpecification(int id) : base(x => x.Id == id)
{
AddInclude(x => x.ProductType);
AddInclude(x => x.ProductBrand);
}
}
}

View File

@ -0,0 +1,41 @@
using Core.Entities;
using Core.Interfaces;
using Core.Specifications;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Data
{
public class GenericRepository<T> : IGenericRepository<T> where T : BaseEntity
{
private readonly StoreContext _context;
public GenericRepository(StoreContext context)
{
_context = context;
}
public async Task<T> GetByIdAsync(int id)
{
return await _context.Set<T>().FindAsync(id);
}
public async Task<IReadOnlyList<T>> ListAllAsync()
{
return await _context.Set<T>().ToListAsync();
}
public async Task<T> GetEntityWithSpec(ISpecification<T> spec)
{
return await ApplySpecification(spec).FirstOrDefaultAsync();
}
public async Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec)
{
return await ApplySpecification(spec).ToListAsync();
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
return SpecificationEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}
}
}

View File

@ -12,14 +12,30 @@ namespace Infrastructure.Data
_context = context; _context = context;
} }
public async Task<IReadOnlyList<ProductBrand>> GetProductBrandsAsync()
{
return await _context.ProductBrands.ToListAsync();
}
public async Task<Product> GetProductByIdAsync(int id) public async Task<Product> GetProductByIdAsync(int id)
{ {
return await _context.Products.FindAsync(id); return await _context.Products
.Include(p => p.ProductType)
.Include(p => p.ProductBrand)
.FirstOrDefaultAsync(p => p.Id == id);
} }
public async Task<IReadOnlyList<Product>> GetProductsAync() public async Task<IReadOnlyList<Product>> GetProductsAync()
{ {
return await _context.Products.ToListAsync(); return await _context.Products
.Include(p => p.ProductType)
.Include(p => p.ProductBrand)
.ToListAsync();
}
public async Task<IReadOnlyList<ProductType>> GetProductTypesAsync()
{
return await _context.ProductTypes.ToListAsync();
} }
} }
} }

View File

@ -0,0 +1,26 @@
[
{
"Id": 1,
"Name": "Angular"
},
{
"Id": 2,
"Name": "NetCore"
},
{
"Id": 3,
"Name": "VS Code"
},
{
"Id": 4,
"Name": "React"
},
{
"Id": 5,
"Name": "Typescript"
},
{
"Id": 6,
"Name": "Redis"
}
]

View File

@ -0,0 +1,146 @@
[
{
"Name": "Angular Speedster Board 2000",
"Description": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.",
"Price": 200,
"PictureUrl": "images/products/sb-ang1.png",
"ProductTypeId": 1,
"ProductBrandId": 1
},
{
"Name": "Green Angular Board 3000",
"Description": "Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus.",
"Price": 150,
"PictureUrl": "images/products/sb-ang2.png",
"ProductTypeId": 1,
"ProductBrandId": 1
},
{
"Name": "Core Board Speed Rush 3",
"Description": "Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy.",
"Price": 180,
"PictureUrl": "images/products/sb-core1.png",
"ProductTypeId": 1,
"ProductBrandId": 2
},
{
"Name": "Net Core Super Board",
"Description": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci.",
"Price": 300,
"PictureUrl": "images/products/sb-core2.png",
"ProductTypeId": 1,
"ProductBrandId": 2
},
{
"Name": "React Board Super Whizzy Fast",
"Description": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.",
"Price": 250,
"PictureUrl": "images/products/sb-react1.png",
"ProductTypeId": 1,
"ProductBrandId": 4
},
{
"Name": "Typescript Entry Board",
"Description": "Aenean nec lorem. In porttitor. Donec laoreet nonummy augue.",
"Price": 120,
"PictureUrl": "images/products/sb-ts1.png",
"ProductTypeId": 1,
"ProductBrandId": 5
},
{
"Name": "Core Blue Hat",
"Description": "Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.",
"Price": 10,
"PictureUrl": "images/products/hat-core1.png",
"ProductTypeId": 2,
"ProductBrandId": 2
},
{
"Name": "Green React Woolen Hat",
"Description": "Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy.",
"Price": 8,
"PictureUrl": "images/products/hat-react1.png",
"ProductTypeId": 2,
"ProductBrandId": 4
},
{
"Name": "Purple React Woolen Hat",
"Description": "Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.",
"Price": 15,
"PictureUrl": "images/products/hat-react2.png",
"ProductTypeId": 2,
"ProductBrandId": 4
},
{
"Name": "Blue Code Gloves",
"Description": "Nunc viverra imperdiet enim. Fusce est. Vivamus a tellus.",
"Price": 18,
"PictureUrl": "images/products/glove-code1.png",
"ProductTypeId": 4,
"ProductBrandId": 3
},
{
"Name": "Green Code Gloves",
"Description": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci.",
"Price": 15,
"PictureUrl": "images/products/glove-code2.png",
"ProductTypeId": 4,
"ProductBrandId": 3
},
{
"Name": "Purple React Gloves",
"Description": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa.",
"Price": 16,
"PictureUrl": "images/products/glove-react1.png",
"ProductTypeId": 4,
"ProductBrandId": 4
},
{
"Name": "Green React Gloves",
"Description": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci.",
"Price": 14,
"PictureUrl": "images/products/glove-react2.png",
"ProductTypeId": 4,
"ProductBrandId": 4
},
{
"Name": "Redis Red Boots",
"Description": "Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy.",
"Price": 250,
"PictureUrl": "images/products/boot-redis1.png",
"ProductTypeId": 3,
"ProductBrandId": 6
},
{
"Name": "Core Red Boots",
"Description": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis urna.",
"Price": 189.99,
"PictureUrl": "images/products/boot-core2.png",
"ProductTypeId": 3,
"ProductBrandId": 2
},
{
"Name": "Core Purple Boots",
"Description": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Proin pharetra nonummy pede. Mauris et orci.",
"Price": 199.99,
"PictureUrl": "images/products/boot-core1.png",
"ProductTypeId": 3,
"ProductBrandId": 2
},
{
"Name": "Angular Purple Boots",
"Description": "Aenean nec lorem. In porttitor. Donec laoreet nonummy augue.",
"Price": 150,
"PictureUrl": "images/products/boot-ang2.png",
"ProductTypeId": 3,
"ProductBrandId": 1
},
{
"Name": "Angular Blue Boots",
"Description": "Suspendisse dui purus, scelerisque at, vulputate vitae, pretium mattis, nunc. Mauris eget neque at sem venenatis eleifend. Ut nonummy.",
"Price": 180,
"PictureUrl": "images/products/boot-ang1.png",
"ProductTypeId": 3,
"ProductBrandId": 1
}
]

View File

@ -0,0 +1,18 @@
[
{
"Id": 1,
"Name": "Boards"
},
{
"Id": 2,
"Name": "Hats"
},
{
"Id": 3,
"Name": "Boots"
},
{
"Id": 4,
"Name": "Gloves"
}
]

View File

@ -0,0 +1,23 @@
using Core.Entities;
using Core.Specifications;
using Microsoft.EntityFrameworkCore;
namespace Infrastructure.Data
{
public class SpecificationEvaluator<TEntity> where TEntity : BaseEntity
{
public static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery, ISpecification<TEntity> spec)
{
var query = inputQuery;
if (spec.Criteria != null)
{
query = query.Where(spec.Criteria);
}
query = spec.Includes.Aggregate(query, (current, include) => current.Include(include));
return query;
}
}
}

View File

@ -0,0 +1,57 @@
using System.Text.Json;
using Core.Entities;
using Infrastructure.Data;
using Microsoft.Extensions.Logging;
namespace Infrastructure
{
public class StoreContextSeed
{
public static async Task SeedAsync(StoreContext context, ILoggerFactory loggerFactory)
{
try
{
if (!context.ProductBrands.Any())
{
var brandsData = File.ReadAllText("../Infrastructure/Data/SeedData/brands.json");
var brands = JsonSerializer.Deserialize<List<ProductBrand>>(brandsData);
foreach (var item in brands)
{
context.ProductBrands.Add(item);
}
await context.SaveChangesAsync();
}
if (!context.ProductTypes.Any())
{
var typesData = File.ReadAllText("../Infrastructure/Data/SeedData/types.json");
var types = JsonSerializer.Deserialize<List<ProductType>>(typesData);
foreach (var item in types)
{
context.ProductTypes.Add(item);
}
await context.SaveChangesAsync();
}
if (!context.Products.Any())
{
var productsData = File.ReadAllText("../Infrastructure/Data/SeedData/products.json");
var products = JsonSerializer.Deserialize<List<Product>>(productsData);
foreach (var item in products)
{
context.Products.Add(item);
}
await context.SaveChangesAsync();
}
}
catch (Exception ex)
{
var logger = loggerFactory.CreateLogger<StoreContextSeed>();
logger.LogError(ex.Message);
}
}
}
}