Added Sorting, Filtering, Pagination and Cors

This commit is contained in:
Charles Showalter 2022-05-11 16:24:26 -07:00
parent bafe874f96
commit 5f9330f20b
13 changed files with 172 additions and 14 deletions

View File

@ -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}")]

View File

@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Extensions
{
public static class ApplicationServicesExtensions
public static class ApplicationServicesExtensions
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{

23
API/Helpers/Pagination.cs Normal file
View 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;}
}
}

View File

@ -24,24 +24,27 @@ 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");
});
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
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();

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View 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();
}
}
}

View File

@ -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)
)
{
}
}
}

View File

@ -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)

View File

@ -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)
{

View File

@ -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;

View File

@ -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>();
}
}
}
}
}
}