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 API.Dtos;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using API.Errors;
|
using API.Errors;
|
||||||
|
using API.Helpers;
|
||||||
|
|
||||||
namespace API.Controllers
|
namespace API.Controllers
|
||||||
{
|
{
|
||||||
@ -24,11 +25,14 @@ namespace API.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[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);
|
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}")]
|
[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.AddSwaggerDocumentation();
|
||||||
services.AddDbContext<StoreContext>(x => x.UseSqlite(_config.GetConnectionString("DefaultConnection")));
|
services.AddDbContext<StoreContext>(x => x.UseSqlite(_config.GetConnectionString("DefaultConnection")));
|
||||||
services.AddAutoMapper(typeof(MappingProfiles));
|
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)
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
{
|
{
|
||||||
app.UseMiddleware<ExceptionMiddleware>();
|
app.UseMiddleware<ExceptionMiddleware>();
|
||||||
|
|
||||||
app.UseStatusCodePagesWithReExecute("/errors/{0}");
|
app.UseStatusCodePagesWithReExecute("/errors/{0}");
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
|
app.UseCors("CorsPolicy");
|
||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
app.UseSwaggerDocumentation();
|
app.UseSwaggerDocumentation();
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
endpoints.MapControllers();
|
endpoints.MapControllers();
|
||||||
|
@ -9,5 +9,6 @@ namespace Core.Interfaces
|
|||||||
Task<IReadOnlyList<T>> ListAllAsync();
|
Task<IReadOnlyList<T>> ListAllAsync();
|
||||||
Task<T> GetEntityWithSpec(ISpecification<T> spec);
|
Task<T> GetEntityWithSpec(ISpecification<T> spec);
|
||||||
Task<IReadOnlyList<T>> ListAsync(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 Expression<Func<T, bool>> Criteria { get; }
|
||||||
public List<Expression<Func<T, object>>> Includes { get; } =
|
public List<Expression<Func<T, object>>> Includes { get; } =
|
||||||
new List<Expression<Func<T, object>>>();
|
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)
|
protected void AddInclude(Expression<Func<T, object>> includeExpression)
|
||||||
{
|
{
|
||||||
Includes.Add(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>
|
public interface ISpecification<T>
|
||||||
{
|
{
|
||||||
Expression<Func<T, bool>> Criteria {get; }
|
Expression<Func<T, bool>> Criteria { get; }
|
||||||
List<Expression<Func<T, object>>> Includes {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 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.ProductType);
|
||||||
AddInclude(x => x.ProductBrand);
|
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)
|
public ProductsWithTypesAndBrandsSpecification(int id) : base(x => x.Id == id)
|
||||||
|
@ -32,6 +32,10 @@ namespace Infrastructure.Data
|
|||||||
{
|
{
|
||||||
return await ApplySpecification(spec).ToListAsync();
|
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)
|
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
|
||||||
{
|
{
|
||||||
|
@ -15,6 +15,21 @@ namespace Infrastructure.Data
|
|||||||
query = query.Where(spec.Criteria);
|
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));
|
query = spec.Includes.Aggregate(query, (current, include) => current.Include(include));
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
@ -16,6 +16,18 @@ namespace Infrastructure.Data
|
|||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
|
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