diff --git a/src/Application/Common/Behaviours/AuthorizationBehaviour.cs b/src/Application/Common/Behaviours/AuthorizationBehaviour.cs index a42419ef0..efef45ea7 100644 --- a/src/Application/Common/Behaviours/AuthorizationBehaviour.cs +++ b/src/Application/Common/Behaviours/AuthorizationBehaviour.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using CleanArchitecture.Blazor.Application.Common.ExceptionHandlers; using CleanArchitecture.Blazor.Application.Common.Interfaces.Identity; using CleanArchitecture.Blazor.Application.Common.Security; diff --git a/src/Application/Common/Extensions/QueryableExtensions.cs b/src/Application/Common/Extensions/QueryableExtensions.cs index ece1c47b9..acd0fdf9e 100644 --- a/src/Application/Common/Extensions/QueryableExtensions.cs +++ b/src/Application/Common/Extensions/QueryableExtensions.cs @@ -2,16 +2,50 @@ // The .NET Foundation licenses this file to you under the MIT license. using Ardalis.Specification.EntityFrameworkCore; -using Ardalis.Specification; using CleanArchitecture.Blazor.Domain.Common; -using DocumentFormat.OpenXml.Bibliography; namespace CleanArchitecture.Blazor.Application.Common.Extensions; public static class QueryableExtensions { - public static IQueryable Specify(this IQueryable query, ISpecification spec, bool evaluateCriteriaOnly=false) where T : class, IEntity + /// + /// Extension method to provided query by specification. + /// + /// + /// This method uses the SpecificationEvaluator to evaluate and modify the provided query based on the given specification. + /// + /// Type of the entities in the query + /// The original query to which the specification should be applied + /// The specification to apply to the query + /// Optional flag to determine if only the criteria should be evaluated or other configurations as well + /// The modified query with the specification applied + public static IQueryable ApplySpecification(this IQueryable query, ISpecification spec, bool evaluateCriteriaOnly=false) where T : class, IEntity { - return new SpecificationEvaluator().GetQuery(query, spec, evaluateCriteriaOnly); + return SpecificationEvaluator.Default.GetQuery(query, spec, evaluateCriteriaOnly); + } + /// + /// Extension method to provided ordered queryable data to a paginated result set. + /// + /// + /// This method will apply the given specification to the query, paginate the results, and project them to the desired result type. + /// + /// Source type of the entities in the query + /// Destination type to which the entities should be projected + /// The original ordered query to project and paginate + /// The specification to apply to the query before projection and pagination + /// The desired page number of the paginated results + /// The number of items per page in the paginated results + /// Configuration for the projection + /// Optional cancellation token to cancel the operation + /// The paginated and projected data + public static async Task> ProjectToPaginatedDataAsync(this IOrderedQueryable query, ISpecification spec,int pageNumber,int pageSize, IConfigurationProvider configuration, CancellationToken cancellationToken = default) where T : class, IEntity + { + + var specificationEvaluator = SpecificationEvaluator.Default; + var count =await specificationEvaluator.GetQuery(query,spec).CountAsync(); + var data = await specificationEvaluator.GetQuery(query.AsNoTracking(), spec).Skip((pageNumber-1)* pageSize).Take(pageSize) + .ProjectTo(configuration) + .ToListAsync(cancellationToken); + return new PaginatedData(data, count, pageNumber, pageSize); } } diff --git a/src/Application/Common/Mappings/MappingExtensions.cs b/src/Application/Common/Mappings/MappingExtensions.cs index af60b4b3b..2c9f2332f 100644 --- a/src/Application/Common/Mappings/MappingExtensions.cs +++ b/src/Application/Common/Mappings/MappingExtensions.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Ardalis.Specification; - namespace CleanArchitecture.Blazor.Application.Common.Mappings; public static class MappingExtensions diff --git a/src/Application/Features/AuditTrails/Queries/PaginationQuery/AuditTrailsWithPaginationQuery.cs b/src/Application/Features/AuditTrails/Queries/PaginationQuery/AuditTrailsWithPaginationQuery.cs index de6c5e340..45f9f0548 100644 --- a/src/Application/Features/AuditTrails/Queries/PaginationQuery/AuditTrailsWithPaginationQuery.cs +++ b/src/Application/Features/AuditTrails/Queries/PaginationQuery/AuditTrailsWithPaginationQuery.cs @@ -3,8 +3,6 @@ using CleanArchitecture.Blazor.Application.Features.AuditTrails.Caching; using CleanArchitecture.Blazor.Application.Features.AuditTrails.DTOs; -using CleanArchitecture.Blazor.Application.Features.Documents.Queries.PaginationQuery; -using CleanArchitecture.Blazor.Application.Features.Products.Queries.Specification; using CleanArchitecture.Blazor.Domain.Enums; namespace CleanArchitecture.Blazor.Application.Features.AuditTrails.Queries.PaginationQuery; @@ -44,10 +42,8 @@ IMapper mapper public async Task> Handle(AuditTrailsWithPaginationQuery request, CancellationToken cancellationToken) { - var data = await _context.AuditTrails.Specify(request.Specification) - .OrderBy($"{request.OrderBy} {request.SortDirection}") - .ProjectTo(_mapper.ConfigurationProvider) - .PaginatedDataAsync(request.PageNumber, request.PageSize); + var data = await _context.AuditTrails.OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, _mapper.ConfigurationProvider, cancellationToken); return data; } diff --git a/src/Application/Features/Customers/Commands/Create/CreateCustomerCommand.cs b/src/Application/Features/Customers/Commands/Create/CreateCustomerCommand.cs index 17f61a1ee..79c7dddb9 100644 --- a/src/Application/Features/Customers/Commands/Create/CreateCustomerCommand.cs +++ b/src/Application/Features/Customers/Commands/Create/CreateCustomerCommand.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.ComponentModel; using CleanArchitecture.Blazor.Application.Features.Customers.DTOs; using CleanArchitecture.Blazor.Application.Features.Customers.Caching; diff --git a/src/Application/Features/Customers/Commands/Update/UpdateCustomerCommand.cs b/src/Application/Features/Customers/Commands/Update/UpdateCustomerCommand.cs index eafb6dc24..2fd391010 100644 --- a/src/Application/Features/Customers/Commands/Update/UpdateCustomerCommand.cs +++ b/src/Application/Features/Customers/Commands/Update/UpdateCustomerCommand.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.ComponentModel; using CleanArchitecture.Blazor.Application.Features.Customers.DTOs; using CleanArchitecture.Blazor.Application.Features.Customers.Caching; diff --git a/src/Application/Features/Customers/DTOs/CustomerDto.cs b/src/Application/Features/Customers/DTOs/CustomerDto.cs index 15785e31c..3e2b86e81 100644 --- a/src/Application/Features/Customers/DTOs/CustomerDto.cs +++ b/src/Application/Features/Customers/DTOs/CustomerDto.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.ComponentModel; namespace CleanArchitecture.Blazor.Application.Features.Customers.DTOs; [Description("Customers")] diff --git a/src/Application/Features/Customers/Queries/Export/ExportCustomersQuery.cs b/src/Application/Features/Customers/Queries/Export/ExportCustomersQuery.cs index bcd1d43f1..575b636be 100644 --- a/src/Application/Features/Customers/Queries/Export/ExportCustomersQuery.cs +++ b/src/Application/Features/Customers/Queries/Export/ExportCustomersQuery.cs @@ -40,7 +40,7 @@ IStringLocalizer localizer public async Task> Handle(ExportCustomersQuery request, CancellationToken cancellationToken) { // TODO: Implement ExportCustomersQueryHandler method - var data = await _context.Customers.Specify(request.Specification) + var data = await _context.Customers.ApplySpecification(request.Specification) .OrderBy($"{request.OrderBy} {request.SortDirection}") .ProjectTo(_mapper.ConfigurationProvider) .AsNoTracking() diff --git a/src/Application/Features/Customers/Queries/Pagination/CustomersPaginationQuery.cs b/src/Application/Features/Customers/Queries/Pagination/CustomersPaginationQuery.cs index b1dee7740..385771e34 100644 --- a/src/Application/Features/Customers/Queries/Pagination/CustomersPaginationQuery.cs +++ b/src/Application/Features/Customers/Queries/Pagination/CustomersPaginationQuery.cs @@ -40,11 +40,9 @@ IStringLocalizer localizer public async Task> Handle(CustomersWithPaginationQuery request, CancellationToken cancellationToken) { // TODO: Implement CustomersWithPaginationQueryHandler method - var data = await _context.Customers.Specify(request.Specification) - .OrderBy($"{request.OrderBy} {request.SortDirection}") - .ProjectTo(_mapper.ConfigurationProvider) - .PaginatedDataAsync(request.PageNumber, request.PageSize); - return data; + var data = await _context.Customers.OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, _mapper.ConfigurationProvider, cancellationToken); + return data; } } diff --git a/src/Application/Features/Documents/Commands/AddEdit/AddEditDocumentCommand.cs b/src/Application/Features/Documents/Commands/AddEdit/AddEditDocumentCommand.cs index 9ba700d78..6ea509af4 100644 --- a/src/Application/Features/Documents/Commands/AddEdit/AddEditDocumentCommand.cs +++ b/src/Application/Features/Documents/Commands/AddEdit/AddEditDocumentCommand.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using CleanArchitecture.Blazor.Application.Common.ExceptionHandlers; using CleanArchitecture.Blazor.Application.Features.Documents.Caching; using CleanArchitecture.Blazor.Application.Features.Documents.DTOs; using CleanArchitecture.Blazor.Domain.Enums; diff --git a/src/Application/Features/Documents/Queries/PaginationQuery/DocumentsWithPaginationQuery.cs b/src/Application/Features/Documents/Queries/PaginationQuery/DocumentsWithPaginationQuery.cs index b89196863..cc1f25c4d 100644 --- a/src/Application/Features/Documents/Queries/PaginationQuery/DocumentsWithPaginationQuery.cs +++ b/src/Application/Features/Documents/Queries/PaginationQuery/DocumentsWithPaginationQuery.cs @@ -39,10 +39,8 @@ IMapper mapper public async Task> Handle(DocumentsWithPaginationQuery request, CancellationToken cancellationToken) { - var data = await _context.Documents.Specify(request.Specification) - .OrderBy($"{request.OrderBy} {request.SortDirection}") - .ProjectTo(_mapper.ConfigurationProvider) - .PaginatedDataAsync(request.PageNumber,request.PageSize); + var data = await _context.Documents.OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, _mapper.ConfigurationProvider, cancellationToken); return data; } diff --git a/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs b/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs index cf9810368..c1c0f0ca1 100644 --- a/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs +++ b/src/Application/Features/KeyValues/Commands/AddEdit/AddEditKeyValueCommand.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using CleanArchitecture.Blazor.Application.Common.ExceptionHandlers; using CleanArchitecture.Blazor.Application.Features.KeyValues.Caching; using CleanArchitecture.Blazor.Application.Features.KeyValues.DTOs; using CleanArchitecture.Blazor.Domain.Enums; diff --git a/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs b/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs index 9c07e2a06..7dfa7f6aa 100644 --- a/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs +++ b/src/Application/Features/KeyValues/Queries/PaginationQuery/KeyValuesWithPaginationQuery.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using CleanArchitecture.Blazor.Application.Features.Documents.DTOs; -using CleanArchitecture.Blazor.Application.Features.Documents.Queries.PaginationQuery; using CleanArchitecture.Blazor.Application.Features.KeyValues.Caching; using CleanArchitecture.Blazor.Application.Features.KeyValues.DTOs; using CleanArchitecture.Blazor.Domain.Enums; @@ -38,9 +36,8 @@ IMapper mapper public async Task> Handle(KeyValuesWithPaginationQuery request, CancellationToken cancellationToken) { - var data = await _context.KeyValues.Specify(request.Specification) - .ProjectTo(_mapper.ConfigurationProvider) - .PaginatedDataAsync(request.PageNumber, request.PageSize); + var data = await _context.KeyValues.OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, _mapper.ConfigurationProvider, cancellationToken); return data; } diff --git a/src/Application/Features/Loggers/Queries/PaginationQuery/LogsWithPaginationQuery.cs b/src/Application/Features/Loggers/Queries/PaginationQuery/LogsWithPaginationQuery.cs index 37a8bdc51..ad9579c06 100644 --- a/src/Application/Features/Loggers/Queries/PaginationQuery/LogsWithPaginationQuery.cs +++ b/src/Application/Features/Loggers/Queries/PaginationQuery/LogsWithPaginationQuery.cs @@ -1,10 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using CleanArchitecture.Blazor.Application.Features.Documents.Queries.PaginationQuery; using CleanArchitecture.Blazor.Application.Features.Loggers.Caching; using CleanArchitecture.Blazor.Application.Features.Loggers.DTOs; -using CleanArchitecture.Blazor.Application.Features.Products.Queries.Specification; using CleanArchitecture.Blazor.Domain.Entities.Logger; namespace CleanArchitecture.Blazor.Application.Features.Loggers.Queries.PaginationQuery; @@ -39,10 +37,8 @@ IMapper mapper public async Task> Handle(LogsWithPaginationQuery request, CancellationToken cancellationToken) { - var data = await _context.Loggers.Specify(request.Specification) - .ProjectTo(_mapper.ConfigurationProvider) - .PaginatedDataAsync(request.PageNumber, request.PageSize); - + var data = await _context.Loggers.OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, _mapper.ConfigurationProvider, cancellationToken); return data; } } diff --git a/src/Application/Features/Products/Caching/ProductCacheKey.cs b/src/Application/Features/Products/Caching/ProductCacheKey.cs index dd71e6647..06a52ef04 100644 --- a/src/Application/Features/Products/Caching/ProductCacheKey.cs +++ b/src/Application/Features/Products/Caching/ProductCacheKey.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - -using DocumentFormat.OpenXml.Spreadsheet; - namespace CleanArchitecture.Blazor.Application.Features.Products.Caching; public static class ProductCacheKey diff --git a/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs b/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs index ff198cae9..653a97258 100644 --- a/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs +++ b/src/Application/Features/Products/Commands/AddEdit/AddEditProductCommand.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. -using CleanArchitecture.Blazor.Application.Common.ExceptionHandlers; using CleanArchitecture.Blazor.Application.Features.Products.Caching; using CleanArchitecture.Blazor.Application.Features.Products.DTOs; using Microsoft.AspNetCore.Components.Forms; diff --git a/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs b/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs index 8c2b2eb6a..a825f6fd1 100644 --- a/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs +++ b/src/Application/Features/Products/Queries/Export/ExportProductsQuery.cs @@ -56,9 +56,9 @@ IStringLocalizer localizer public async Task> Handle(ExportProductsQuery request, CancellationToken cancellationToken) { - var data = await _context.Products.Specify(request.Specification) - .ProjectTo(_mapper.ConfigurationProvider) + var data = await _context.Products.ApplySpecification(request.Specification) .AsNoTracking() + .ProjectTo(_mapper.ConfigurationProvider) .ToListAsync(cancellationToken); diff --git a/src/Application/Features/Products/Queries/Pagination/ProductsPaginationQuery.cs b/src/Application/Features/Products/Queries/Pagination/ProductsPaginationQuery.cs index 7a0437e45..3ccebe65e 100644 --- a/src/Application/Features/Products/Queries/Pagination/ProductsPaginationQuery.cs +++ b/src/Application/Features/Products/Queries/Pagination/ProductsPaginationQuery.cs @@ -54,11 +54,9 @@ IStringLocalizer localizer public async Task> Handle(ProductsWithPaginationQuery request, CancellationToken cancellationToken) { - - var data = await _context.Products.Specify(request.Specification) - .OrderBy($"{request.OrderBy} {request.SortDirection}") - .ProjectTo(_mapper.ConfigurationProvider) - .PaginatedDataAsync(request.PageNumber, request.PageSize); + + var data = await _context.Products.OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, _mapper.ConfigurationProvider, cancellationToken); return data; } } \ No newline at end of file diff --git a/src/Application/Features/Products/Queries/Specification/SearchProductSpecification.cs b/src/Application/Features/Products/Queries/Specification/SearchProductSpecification.cs index 4db5ca421..64136e135 100644 --- a/src/Application/Features/Products/Queries/Specification/SearchProductSpecification.cs +++ b/src/Application/Features/Products/Queries/Specification/SearchProductSpecification.cs @@ -25,5 +25,7 @@ public SearchProductSpecification(ProductsWithPaginationQuery query) .Where(product => product.CreatedBy == query.CurrentUser.UserId, query.ListView == ProductListView.My) .Where(product => product.Created >= start && product.Created <= end, query.ListView == ProductListView.CreatedToday) .Where(product => product.Created >= last30day, query.ListView == ProductListView.Created30Days); + + } } diff --git a/src/Application/Features/Tenants/Queries/Pagination/TenantsPaginationQuery.cs b/src/Application/Features/Tenants/Queries/Pagination/TenantsPaginationQuery.cs index 1b5d67b89..bb029acbc 100644 --- a/src/Application/Features/Tenants/Queries/Pagination/TenantsPaginationQuery.cs +++ b/src/Application/Features/Tenants/Queries/Pagination/TenantsPaginationQuery.cs @@ -15,6 +15,7 @@ public override string ToString() { return $"Search:{Keyword},OrderBy:{OrderBy} {SortDirection},{PageNumber},{PageSize}"; } + public TenantsPaginationSpecification Specification => new TenantsPaginationSpecification(this); } public class TenantsWithPaginationQueryHandler : @@ -38,9 +39,17 @@ IStringLocalizer localizer public async Task> Handle(TenantsWithPaginationQuery request, CancellationToken cancellationToken) { - var data = await _context.Tenants.Where(x=>x.Name.Contains(request.Keyword) || x.Description.Contains(request.Keyword)) - .ProjectTo(_mapper.ConfigurationProvider) - .PaginatedDataAsync(request.PageNumber, request.PageSize); + var data = await _context.Tenants.OrderBy($"{request.OrderBy} {request.SortDirection}") + .ProjectToPaginatedDataAsync(request.Specification, request.PageNumber, request.PageSize, _mapper.ConfigurationProvider, cancellationToken); return data; } +} + +public class TenantsPaginationSpecification : Specification +{ + public TenantsPaginationSpecification(TenantsWithPaginationQuery query) + { + Query.Where(q => q.Name != null) + .Where(q => q.Name.Contains(query.Keyword) || q.Description.Contains(query.Keyword), !string.IsNullOrEmpty(query.Keyword)); + } } \ No newline at end of file diff --git a/src/Application/_Imports.cs b/src/Application/_Imports.cs index 7e4ae0ee9..a2b2610aa 100644 --- a/src/Application/_Imports.cs +++ b/src/Application/_Imports.cs @@ -17,10 +17,8 @@ global using Microsoft.Extensions.Logging; global using Microsoft.Extensions.Primitives; global using Microsoft.Extensions.Caching.Memory; -global using CleanArchitecture.Blazor.Domain; global using CleanArchitecture.Blazor.Domain.Entities; global using CleanArchitecture.Blazor.Domain.Events; -global using CleanArchitecture.Blazor.Application.Common.Mappings; global using CleanArchitecture.Blazor.Application.Common.Models; global using CleanArchitecture.Blazor.Application.Common.Extensions; global using CleanArchitecture.Blazor.Application.Common.Interfaces;