Added Sorting, Filtering, Pagination and Cors
This commit is contained in:
parent
bafe874f96
commit
5f9330f20b
@ -5,6 +5,7 @@ using Core.Specifications;
|
||||
using API.Dtos;
|
||||
using AutoMapper;
|
||||
using API.Errors;
|
||||
using API.Helpers;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
@ -24,11 +25,14 @@ namespace API.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IReadOnlyList<ProductToReturnDto>>> GetProducts()
|
||||
public async Task<ActionResult<IReadOnlyList<Pagination<ProductToReturnDto>>>> GetProducts([FromQuery]ProductSpecParams productParams)
|
||||
{
|
||||
var spec = new ProductsWithTypesAndBrandsSpecification();
|
||||
var spec = new ProductsWithTypesAndBrandsSpecification(productParams);
|
||||
var countSpec = new ProductWithFiltersForCountSpecifications(productParams);
|
||||
var totalItems = await _productsRepo.CountAsync(countSpec);
|
||||
var products = await _productsRepo.ListAsync(spec);
|
||||
return Ok(_mapper.Map<IReadOnlyList<Product>, IReadOnlyList<ProductToReturnDto>>(products));
|
||||
var data = _mapper.Map<IReadOnlyList<Product>, IReadOnlyList<ProductToReturnDto>>(products);
|
||||
return Ok(new Pagination<ProductToReturnDto>(productParams.PageIndex, productParams.PageSize, totalItems, data));
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
|
23
API/Helpers/Pagination.cs
Normal file
23
API/Helpers/Pagination.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace API.Helpers
|
||||
{
|
||||
public class Pagination<T> where T : class
|
||||
{
|
||||
public Pagination(int pageIndex, int pageSize, int count, IReadOnlyList<T> data)
|
||||
{
|
||||
PageIndex = pageIndex;
|
||||
PageSize = pageSize;
|
||||
Count = count;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public int PageIndex { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int Count { get; set; }
|
||||
public IReadOnlyList<T> Data {get; set;}
|
||||
}
|
||||
}
|
@ -24,6 +24,13 @@ namespace API
|
||||
services.AddSwaggerDocumentation();
|
||||
services.AddDbContext<StoreContext>(x => x.UseSqlite(_config.GetConnectionString("DefaultConnection")));
|
||||
services.AddAutoMapper(typeof(MappingProfiles));
|
||||
services.AddCors(opt =>
|
||||
{
|
||||
opt.AddPolicy("CorsPolicy", policy =>
|
||||
{
|
||||
policy.AllowAnyHeader().AllowAnyMethod().WithOrigins("https://localhost:4200");
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -31,17 +38,13 @@ namespace API
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
app.UseMiddleware<ExceptionMiddleware>();
|
||||
|
||||
app.UseStatusCodePagesWithReExecute("/errors/{0}");
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseCors("CorsPolicy");
|
||||
app.UseAuthorization();
|
||||
app.UseSwaggerDocumentation();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
|
@ -9,5 +9,6 @@ namespace Core.Interfaces
|
||||
Task<IReadOnlyList<T>> ListAllAsync();
|
||||
Task<T> GetEntityWithSpec(ISpecification<T> spec);
|
||||
Task<IReadOnlyList<T>> ListAsync(ISpecification<T> spec);
|
||||
Task<int> CountAsync(ISpecification<T> spec);
|
||||
}
|
||||
}
|
@ -15,9 +15,37 @@ namespace Core.Specifications
|
||||
public Expression<Func<T, bool>> Criteria { get; }
|
||||
public List<Expression<Func<T, object>>> Includes { get; } =
|
||||
new List<Expression<Func<T, object>>>();
|
||||
|
||||
public Expression<Func<T, object>> OrderBy {get; private set;}
|
||||
|
||||
public Expression<Func<T, object>> OrderByDecending {get; private set;}
|
||||
|
||||
public int Take {get; private set;}
|
||||
|
||||
public int Skip {get; private set;}
|
||||
|
||||
public bool IsPagingEnabled {get; private set;}
|
||||
|
||||
protected void AddInclude(Expression<Func<T, object>> includeExpression)
|
||||
{
|
||||
Includes.Add(includeExpression);
|
||||
}
|
||||
|
||||
protected void AddOrderBy(Expression<Func<T, object>> orderByExpression)
|
||||
{
|
||||
OrderBy = orderByExpression;
|
||||
}
|
||||
|
||||
protected void AddOrdeByDescending(Expression<Func<T, object>> orderByDescExpression)
|
||||
{
|
||||
OrderByDecending = orderByDescExpression;
|
||||
}
|
||||
|
||||
protected void ApplyPaging(int skip, int take)
|
||||
{
|
||||
Skip = skip;
|
||||
Take = take;
|
||||
IsPagingEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,8 +4,13 @@ namespace Core.Specifications
|
||||
{
|
||||
public interface ISpecification<T>
|
||||
{
|
||||
Expression<Func<T, bool>> Criteria {get; }
|
||||
List<Expression<Func<T, object>>> Includes {get; }
|
||||
Expression<Func<T, bool>> Criteria { get; }
|
||||
List<Expression<Func<T, object>>> Includes { get; }
|
||||
Expression<Func<T, object>> OrderBy { get; }
|
||||
Expression<Func<T, object>> OrderByDecending { get; }
|
||||
|
||||
int Take {get; }
|
||||
int Skip {get; }
|
||||
bool IsPagingEnabled {get; }
|
||||
}
|
||||
}
|
25
Core/Specifications/ProductSpecParams.cs
Normal file
25
Core/Specifications/ProductSpecParams.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Core.Specifications
|
||||
{
|
||||
public class ProductSpecParams
|
||||
{
|
||||
private const int MaxPageSize = 50;
|
||||
public int PageIndex {get; set;} =1;
|
||||
|
||||
private int _pageSize = 6;
|
||||
public int PageSize
|
||||
{
|
||||
get => _pageSize;
|
||||
set => _pageSize = (value > MaxPageSize) ? MaxPageSize : value;
|
||||
}
|
||||
public int? BrandId { get; set; }
|
||||
public int? TypeId { get; set; }
|
||||
public string Sort { get; set; }
|
||||
|
||||
private string _search;
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set => _search = value.ToLower();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
using Core.Entities;
|
||||
|
||||
namespace Core.Specifications
|
||||
{
|
||||
public class ProductWithFiltersForCountSpecifications : BaseSpecification<Product>
|
||||
{
|
||||
public ProductWithFiltersForCountSpecifications(ProductSpecParams productParams)
|
||||
: base(x =>
|
||||
(string.IsNullOrEmpty(productParams.Search) || x.Name.ToLower().Contains(productParams.Search)) &&
|
||||
(!productParams.BrandId.HasValue || x.ProductBrandId == productParams.BrandId) &&
|
||||
(!productParams.TypeId.HasValue || x.ProductTypeId == productParams.TypeId)
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -5,10 +5,32 @@ namespace Core.Specifications
|
||||
{
|
||||
public class ProductsWithTypesAndBrandsSpecification : BaseSpecification<Product>
|
||||
{
|
||||
public ProductsWithTypesAndBrandsSpecification()
|
||||
public ProductsWithTypesAndBrandsSpecification(ProductSpecParams productParams)
|
||||
: base(x =>
|
||||
(string.IsNullOrEmpty(productParams.Search) || x.Name.ToLower().Contains(productParams.Search)) &&
|
||||
(!productParams.BrandId.HasValue || x.ProductBrandId == productParams.BrandId) &&
|
||||
(!productParams.TypeId.HasValue || x.ProductTypeId == productParams.TypeId)
|
||||
)
|
||||
{
|
||||
AddInclude(x => x.ProductType);
|
||||
AddInclude(x => x.ProductBrand);
|
||||
ApplyPaging(productParams.PageSize * (productParams.PageIndex -1), productParams.PageSize);
|
||||
|
||||
if (!string.IsNullOrEmpty(productParams.Sort))
|
||||
{
|
||||
switch (productParams.Sort)
|
||||
{
|
||||
case "priceAsc":
|
||||
AddOrderBy(p => p.Price);
|
||||
break;
|
||||
case "priceDesc":
|
||||
AddOrdeByDescending(p => p.Price);
|
||||
break;
|
||||
default:
|
||||
AddOrderBy(n => n.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ProductsWithTypesAndBrandsSpecification(int id) : base(x => x.Id == id)
|
||||
|
@ -32,6 +32,10 @@ namespace Infrastructure.Data
|
||||
{
|
||||
return await ApplySpecification(spec).ToListAsync();
|
||||
}
|
||||
public async Task<int> CountAsync(ISpecification<T> spec)
|
||||
{
|
||||
return await ApplySpecification(spec).CountAsync();
|
||||
}
|
||||
|
||||
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
|
||||
{
|
||||
|
@ -15,6 +15,21 @@ namespace Infrastructure.Data
|
||||
query = query.Where(spec.Criteria);
|
||||
}
|
||||
|
||||
if (spec.OrderBy != null)
|
||||
{
|
||||
query = query.OrderBy(spec.OrderBy);
|
||||
}
|
||||
|
||||
if (spec.OrderByDecending != null)
|
||||
{
|
||||
query = query.OrderByDescending(spec.OrderByDecending);
|
||||
}
|
||||
|
||||
if (spec.IsPagingEnabled)
|
||||
{
|
||||
query = query.Skip(spec.Skip).Take(spec.Take);
|
||||
}
|
||||
|
||||
query = spec.Includes.Aggregate(query, (current, include) => current.Include(include));
|
||||
|
||||
return query;
|
||||
|
@ -16,6 +16,18 @@ namespace Infrastructure.Data
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
|
||||
|
||||
if(Database.ProviderName == "Microsoft.EntityFrameworkCore.Sqlite")
|
||||
{
|
||||
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(decimal));
|
||||
foreach (var property in properties)
|
||||
{
|
||||
modelBuilder.Entity(entityType.Name).Property(property.Name).HasConversion<double>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user