diff --git a/API/Dispo.API/Controllers/DatatableController.cs b/API/Dispo.API/Controllers/DatatableController.cs new file mode 100644 index 0000000..b8108f4 --- /dev/null +++ b/API/Dispo.API/Controllers/DatatableController.cs @@ -0,0 +1,105 @@ +using Dispo.API.ResponseBuilder; +using Dispo.Shared.Core.Domain.Interfaces; +using Dispo.Shared.Filter.Model; +using Dispo.Shared.Filter.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Dispo.API.Controllers +{ + [Route("api/v1/datatable")] + [ApiController] + [Authorize] + public class DatatableController : ControllerBase + { + private readonly IDatatableRepository _datatableRepository; + private readonly IFilterService _filterService; + + public DatatableController(IDatatableRepository datatableRepository, IFilterService filterService) + { + _datatableRepository = datatableRepository; + _filterService = filterService; + } + + [HttpGet("get-count")] + public IActionResult GetCount([FromQuery] string entity) + { + var type = Type.GetType($"Dispo.Shared.Core.Domain.Entities.{entity}, Dispo.Shared.Core.Domain"); + if (type is null) + { + return BadRequest("Entidade inválida."); + } + + var method = _datatableRepository.GetType().GetMethod("GetTotalRecords"); + if (method is null) + { + return BadRequest($"Método 'GetTotalRecords' não implementado para a entidade '{entity}'"); + } + + var genericMethod = method.MakeGenericMethod(type); + var result = genericMethod.Invoke(_datatableRepository, new object[] {}); + + return Ok(new ResponseModelBuilder().WithData(result) + .WithSuccess(true) + .WithAlert(AlertType.Success) + .Build()); + } + + [HttpGet("get-all")] + public IActionResult Get([FromQuery] PaginationModel paginationModel) + { + dynamic datatableData = null; + + if (paginationModel.Entity == "Manufacturer") + { + datatableData = _datatableRepository.GetToDatatableManufacturer(paginationModel.PageNumber, paginationModel.PageSize).ToList(); + } + else if (paginationModel.Entity == "Product") + { + datatableData = _datatableRepository.GetToDatatableProduct(paginationModel.PageNumber, paginationModel.PageSize).ToList(); + } + else if (paginationModel.Entity == "Supplier") + { + datatableData = _datatableRepository.GetToDatatableSupplier(paginationModel.PageNumber, paginationModel.PageSize).ToList(); + } + + + return Ok(new ResponseModelBuilder().WithData(datatableData) + .WithSuccess(true) + .WithAlert(AlertType.Success) + .Build()); + } + + [HttpPost("get-by-filter")] + public IActionResult GetByFilter([FromBody] FilterModel filter) + { + try + { + var type = Type.GetType($"Dispo.Shared.Core.Domain.Entities.{filter.Entity}, Dispo.Shared.Core.Domain"); + if (type is null) + { + return BadRequest("Entidade inválida."); + } + + var method = _filterService.GetType().GetMethod("Get"); + if (method is null) + { + return BadRequest($"Método 'Get' não implementado para a entidade '{filter.Entity}'"); + } + + var genericMethod = method.MakeGenericMethod(type); + var result = genericMethod.Invoke(_filterService, new object[] { filter }); + + return Ok(new ResponseModelBuilder().WithData(result) + .WithSuccess(true) + .Build()); + } + catch (Exception ex) + { + return BadRequest(new ResponseModelBuilder().WithMessage(ex.Message) + .WithSuccess(false) + .Build()); ; + } + } + } +} diff --git a/API/Dispo.API/Dispo.API.csproj b/API/Dispo.API/Dispo.API.csproj index 9ffae8d..6800f96 100644 --- a/API/Dispo.API/Dispo.API.csproj +++ b/API/Dispo.API/Dispo.API.csproj @@ -31,6 +31,7 @@ + diff --git a/API/Dispo.API/Dispo.API.sln b/API/Dispo.API/Dispo.API.sln index f540800..32fba8e 100644 --- a/API/Dispo.API/Dispo.API.sln +++ b/API/Dispo.API/Dispo.API.sln @@ -65,7 +65,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dispo.Infra.Infrastructure. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dispo.Infra.Plugin", "..\..\src\Modules\Infra\Dispo.Infra.Plugin\Dispo.Infra.Plugin.csproj", "{FC7852D4-961C-4A2E-A77D-F32F72D12608}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dispo.Shared.Log", "..\..\src\Modules\Shared\Dispo.Shared.Log\Dispo.Shared.Log.csproj", "{9DF2CC91-7E5B-450D-B281-EA8D0C66A474}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dispo.Shared.Log", "..\..\src\Modules\Shared\Dispo.Shared.Log\Dispo.Shared.Log.csproj", "{9DF2CC91-7E5B-450D-B281-EA8D0C66A474}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dispo.Shared.Filter", "..\..\src\Modules\Shared\Dispo.Shared.Filter\Dispo.Shared.Filter.csproj", "{98098519-4E21-4796-A422-0451709588EA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -161,6 +163,10 @@ Global {9DF2CC91-7E5B-450D-B281-EA8D0C66A474}.Debug|Any CPU.Build.0 = Debug|Any CPU {9DF2CC91-7E5B-450D-B281-EA8D0C66A474}.Release|Any CPU.ActiveCfg = Release|Any CPU {9DF2CC91-7E5B-450D-B281-EA8D0C66A474}.Release|Any CPU.Build.0 = Release|Any CPU + {98098519-4E21-4796-A422-0451709588EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98098519-4E21-4796-A422-0451709588EA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98098519-4E21-4796-A422-0451709588EA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98098519-4E21-4796-A422-0451709588EA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -195,6 +201,7 @@ Global {16FD027B-42F0-4176-AD7D-FC83ED649ED5} = {B6DEED8C-370C-4FC7-858B-73A9CCB41F86} {FC7852D4-961C-4A2E-A77D-F32F72D12608} = {B6DEED8C-370C-4FC7-858B-73A9CCB41F86} {9DF2CC91-7E5B-450D-B281-EA8D0C66A474} = {A26E43DA-73D5-449E-BE45-9E7A88E5E4A2} + {98098519-4E21-4796-A422-0451709588EA} = {A26E43DA-73D5-449E-BE45-9E7A88E5E4A2} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3375A49-13C9-4341-A913-D223CE4E41FC} diff --git a/src/Modules/Infra/Dispo.Infra.Core.Application/Dispo.Infra.Core.Application.csproj b/src/Modules/Infra/Dispo.Infra.Core.Application/Dispo.Infra.Core.Application.csproj index 9b2c1f3..50e206a 100644 --- a/src/Modules/Infra/Dispo.Infra.Core.Application/Dispo.Infra.Core.Application.csproj +++ b/src/Modules/Infra/Dispo.Infra.Core.Application/Dispo.Infra.Core.Application.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Dispo.Infra.Infrastructure.Ioc.csproj b/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Dispo.Infra.Infrastructure.Ioc.csproj index 1d01ddc..ba5f395 100644 --- a/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Dispo.Infra.Infrastructure.Ioc.csproj +++ b/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Dispo.Infra.Infrastructure.Ioc.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Injector.cs b/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Injector.cs index d495690..7616429 100644 --- a/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Injector.cs +++ b/src/Modules/Infra/Dispo.Infra.Infrastructure.Ioc/Injector.cs @@ -2,6 +2,7 @@ using Dispo.Infra.Core.Application.Services; using Dispo.Infra.Infrastructure.Persistence.Repositories; using Dispo.Shared.Core.Domain.Interfaces; +using Dispo.Shared.Filter.Services; using Microsoft.Extensions.DependencyInjection; namespace Dispo.Infra.Infrastructure.Ioc @@ -20,6 +21,8 @@ private static void InjectRepositories(IServiceCollection serviceColletion) serviceColletion.AddScoped(); serviceColletion.AddScoped(); serviceColletion.AddScoped(); + + serviceColletion.AddScoped(); } private static void InjectServices(IServiceCollection serviceColletion) @@ -29,6 +32,7 @@ private static void InjectServices(IServiceCollection serviceColletion) serviceColletion.AddScoped(); serviceColletion.AddScoped(); serviceColletion.AddScoped(); + serviceColletion.AddScoped(); } } } \ No newline at end of file diff --git a/src/Modules/Infra/Dispo.Infra.Infrastructure.Persistence/Repositories/DatatableRepository.cs b/src/Modules/Infra/Dispo.Infra.Infrastructure.Persistence/Repositories/DatatableRepository.cs new file mode 100644 index 0000000..0a0024b --- /dev/null +++ b/src/Modules/Infra/Dispo.Infra.Infrastructure.Persistence/Repositories/DatatableRepository.cs @@ -0,0 +1,122 @@ +using Dispo.Shared.Core.Domain.DTOs; +using Dispo.Shared.Core.Domain.Entities; +using Dispo.Shared.Core.Domain.Interfaces; +using Dispo.Shared.Infrastructure.Persistence; +using Dispo.Shared.Infrastructure.Persistence.Context; +using Dispo.Shared.Utils.Extensions; +using Microsoft.EntityFrameworkCore; + +namespace Dispo.Infra.Infrastructure.Persistence.Repositories +{ + public class DatatableRepository : BaseRepository, IDatatableRepository + { + private readonly DispoContext _dispoContext; + + public DatatableRepository(DispoContext dispoContext) : base(dispoContext) + { + _dispoContext = dispoContext; + } + + public int GetTotalRecords() where T : EntityBase + => _dispoContext.Set() + .AsNoTracking() + .Count(); + + //public IEnumerable GetToDatatable(int pageNumber, int pageSize) + // => _dispoContext.Manufacturers.Skip((pageNumber - 1) * pageSize) + // .Take(pageSize) + // .Select(s => new ManufacturerInfoDto() + // { + // Id = s.Id, + // Name = s.Name, + // }) + // .ToList(); + + public IEnumerable GetToDatatableProduct(int pageNumber, int pageSize) + => _dispoContext.Set().Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(product => new ProductDatatableDto + { + Id = product.Id, + Name = product.Name, + PurchasePrice = product.PurchasePrice.ConvertToCurrency(), + SalePrice = product.SalePrice.ConvertToCurrency(), + Category = EnumExtension.ConvertToString(product.Category), + UnitOfMeasurement = EnumExtension.ConvertToString(product.UnitOfMeasurement), + }) + .ToList(); + + public IEnumerable GetToDatatableManufacturer(int pageNumber, int pageSize) + => _dispoContext.Set().Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(manufacturer => new ManufacturerDatatableDto + { + Id = manufacturer.Id, + Name = manufacturer.Name, + }) + .ToList(); + + public IEnumerable GetToDatatableSupplier(int pageNumber, int pageSize) + => _dispoContext.Set().Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(supplier => new SupplierDatatableDto + { + Id = supplier.Id, + Name = supplier.Name, + ContactName = supplier.ContactName, + Cnpj = supplier.Cnpj, + Email = supplier.Email, + Phone = supplier.Phone, + }) + .ToList(); + + + + public IEnumerable GetToDatatable(int pageNumber, int pageSize) where TEntity : EntityBase + => _dispoContext.Set().Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .Select(ConvertToDatabaseDto) + .ToList(); + + private EntityDatatableDto ConvertToDatabaseDto(TEntity entity) where TEntity : EntityBase + { + if (typeof(TEntity) == typeof(Product)) + { + var product = entity as Product; + return new ProductDatatableDto + { + Id = product.Id, + Name = product.Name, + PurchasePrice = product.PurchasePrice.ConvertToCurrency(), + SalePrice = product.SalePrice.ConvertToCurrency(), + Category = EnumExtension.ConvertToString(product.Category), + UnitOfMeasurement = EnumExtension.ConvertToString(product.UnitOfMeasurement), + }; + } + else if (typeof(TEntity) == typeof(Manufacturer)) + { + var manufacturer = entity as Manufacturer; + return new ManufacturerDatatableDto + { + Id = manufacturer.Id, + Name = manufacturer.Name, + }; + } + else if (typeof(TEntity) == typeof(Supplier)) + { + var supplier = entity as Supplier; + return new SupplierDatatableDto + { + Id = supplier.Id, + Name = supplier.Name, + ContactName = supplier.ContactName, + Cnpj = supplier.Cnpj, + Email = supplier.Email, + Phone = supplier.Phone, + }; + } + + throw new NotImplementedException($"Entidade não encontrada {typeof(TEntity)}"); + } + } +} diff --git a/src/Modules/Shared/Dispo.Shared.Core.Domain/DTOs/EntityDatatableDto.cs b/src/Modules/Shared/Dispo.Shared.Core.Domain/DTOs/EntityDatatableDto.cs new file mode 100644 index 0000000..9c79706 --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Core.Domain/DTOs/EntityDatatableDto.cs @@ -0,0 +1,30 @@ +namespace Dispo.Shared.Core.Domain.DTOs +{ + public class EntityDatatableDto + { + public long Id { get; set; } + } + + public class ProductDatatableDto : EntityDatatableDto + { + public string Name { get; set; } + public string PurchasePrice { get; set; } + public string SalePrice { get; set; } + public string UnitOfMeasurement { get; set; } + public string Category { get; set; } + } + + public class ManufacturerDatatableDto : EntityDatatableDto + { + public string Name { get; set; } + } + + public class SupplierDatatableDto : EntityDatatableDto + { + public string Name { get; set; } + public string ContactName { get; set; } + public string Cnpj { get; set; } + public string Email { get; set; } + public string Phone { get; set; } + } +} diff --git a/src/Modules/Shared/Dispo.Shared.Core.Domain/Interfaces/IDatatableRepository.cs b/src/Modules/Shared/Dispo.Shared.Core.Domain/Interfaces/IDatatableRepository.cs new file mode 100644 index 0000000..0f18ddf --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Core.Domain/Interfaces/IDatatableRepository.cs @@ -0,0 +1,14 @@ +using Dispo.Shared.Core.Domain.DTOs; +using Dispo.Shared.Core.Domain.Entities; + +namespace Dispo.Shared.Core.Domain.Interfaces +{ + public interface IDatatableRepository + { + int GetTotalRecords() where T : EntityBase; + IEnumerable GetToDatatable(int pageNumber, int pageSize) where TEntity : EntityBase; + IEnumerable GetToDatatableProduct(int pageNumber, int pageSize); + IEnumerable GetToDatatableManufacturer(int pageNumber, int pageSize); + IEnumerable GetToDatatableSupplier(int pageNumber, int pageSize); + } +} diff --git a/src/Modules/Shared/Dispo.Shared.Filter/Dispo.Shared.Filter.csproj b/src/Modules/Shared/Dispo.Shared.Filter/Dispo.Shared.Filter.csproj new file mode 100644 index 0000000..f7657de --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Filter/Dispo.Shared.Filter.csproj @@ -0,0 +1,14 @@ + + + + net7.0 + enable + enable + + + + + + + + diff --git a/src/Modules/Shared/Dispo.Shared.Filter/Model/FilterModel.cs b/src/Modules/Shared/Dispo.Shared.Filter/Model/FilterModel.cs new file mode 100644 index 0000000..994c5c3 --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Filter/Model/FilterModel.cs @@ -0,0 +1,15 @@ +namespace Dispo.Shared.Filter.Model +{ + public class FilterModel + { + public required string Entity { get; set; } + public required List Properties { get; set; } + public required PaginationFilter PaginationConfig { get; set; } + } + + public class PaginationFilter + { + public int PageNumber { get; set; } + public int PageSize { get; set; } + } +} diff --git a/src/Modules/Shared/Dispo.Shared.Filter/Model/PaginationFilter.cs b/src/Modules/Shared/Dispo.Shared.Filter/Model/PaginationFilter.cs new file mode 100644 index 0000000..54ef0f4 --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Filter/Model/PaginationFilter.cs @@ -0,0 +1,9 @@ +namespace Dispo.Shared.Filter.Model +{ + public class PaginationModel + { + public string Entity { get; set; } + public int PageNumber { get; set; } + public int PageSize { get; set; } + } +} diff --git a/src/Modules/Shared/Dispo.Shared.Filter/Model/PropertyModel.cs b/src/Modules/Shared/Dispo.Shared.Filter/Model/PropertyModel.cs new file mode 100644 index 0000000..d85bcae --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Filter/Model/PropertyModel.cs @@ -0,0 +1,21 @@ +namespace Dispo.Shared.Filter.Model +{ + public class PropertyModel + { + public string Name { get; set; } + public dynamic Value { get; set; } + public SearchType SearchType { get; set; } + } + + public enum SearchType + { + Equals, + Contains, + StartsWith, + EndsWith, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual + } +} diff --git a/src/Modules/Shared/Dispo.Shared.Filter/Services/FilterService.cs b/src/Modules/Shared/Dispo.Shared.Filter/Services/FilterService.cs new file mode 100644 index 0000000..a66b273 --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Filter/Services/FilterService.cs @@ -0,0 +1,159 @@ +using Dispo.Shared.Core.Domain.Entities; +using Dispo.Shared.Core.Domain.Enums; +using Dispo.Shared.Filter.Model; +using Dispo.Shared.Infrastructure.Persistence.Context; +using Microsoft.EntityFrameworkCore; +using System.Linq.Expressions; +using System.Reflection.Metadata; +using System.Text.Json; + +namespace Dispo.Shared.Filter.Services +{ + public class FilterService : IFilterService + { + private const string UnsupportedSearchTypeMessage = "Unsupported search type for property of type:"; + private readonly DispoContext _dispoContext; + + public FilterService(DispoContext dispoContext) + { + _dispoContext = dispoContext; + } + + public object Get(FilterModel filterModel) where T : EntityBase + { + if (filterModel is null) + { + throw new ArgumentNullException(nameof(filterModel)); + } + + var buildExpression = BuildExpression(filterModel); + + var recordCount = _dispoContext.Set() + .AsNoTracking() + .Where(buildExpression) + .Count(); + + var records = _dispoContext.Set() + .AsNoTracking() + .Where(buildExpression) + .Skip((filterModel.PaginationConfig.PageNumber - 1) * filterModel.PaginationConfig.PageSize) + .Take(filterModel.PaginationConfig.PageSize) + .ToList(); + + var obj = new + { + RecordCount = recordCount, + Records = records + }; + + return obj; + } + + private Func BuildExpression(FilterModel filterModel) + { + var parameter = Expression.Parameter(typeof(T), filterModel.Entity); + Expression filterExpression = null; + foreach (var property in filterModel.Properties) + { + JsonElement jsonElementValue = property.Value; + + var valueType = jsonElementValue.ValueKind; + Expression comparison; + var memberExpression = Expression.Property(parameter, property.Name); + + if (valueType == JsonValueKind.String) // String + { + var valor = jsonElementValue.GetString(); + var constant = Expression.Constant(valor); + + comparison = Expression.Call(memberExpression, property.SearchType.ToString(), Type.EmptyTypes, constant); + } + else if (IsNumericType(memberExpression.Type, property.Value, out ConstantExpression convertedConstant) && valueType == JsonValueKind.Number) // Numerico + { + comparison = GetGenericComparisonExpression(memberExpression, property, convertedConstant); + } + else if (valueType == JsonValueKind.Object) // Enum + { + var valor = jsonElementValue.GetProperty("value").GetInt64(); + var convertedValue = Enum.Parse(memberExpression.Type, valor.ToString()); + var constant = Expression.Constant(convertedValue); + comparison = Expression.Equal(memberExpression, constant); + + } + else if (IsDateTimeType(memberExpression.Type, property.Value, out DateTime convertedDateTime)) + { + comparison = GetGenericComparisonExpression(memberExpression, property, Expression.Constant(convertedDateTime)); + } + else + { + throw new NotSupportedException($"{UnsupportedSearchTypeMessage} {property.SearchType}"); + } + + filterExpression = filterExpression == null ? comparison : Expression.And(filterExpression, comparison); + } + + return Expression.Lambda>(filterExpression ?? Expression.Constant(true), parameter).Compile(); + } + + private Expression GetStringComparisonExpression(MemberExpression memberExpression, PropertyModel property) + { + string convertedValue = Convert.ToString(property.Value); + var constant = Expression.Constant(convertedValue); + + // Ao enviar o SearchType.Equals está disparando uma exceção. + return Expression.Call(memberExpression, property.SearchType.ToString(), Type.EmptyTypes, constant); + } + + private Expression GetGenericComparisonExpression(MemberExpression memberExpression, PropertyModel property, ConstantExpression convertedConstant) + { + switch (property.SearchType) + { + case SearchType.Equals: + return Expression.Equal(memberExpression, convertedConstant); + case SearchType.GreaterThan: + return Expression.GreaterThan(memberExpression, convertedConstant); + case SearchType.GreaterThanOrEqual: + return Expression.GreaterThanOrEqual(memberExpression, convertedConstant); + case SearchType.LessThan: + return Expression.LessThan(memberExpression, convertedConstant); + case SearchType.LessThanOrEqual: + return Expression.LessThanOrEqual(memberExpression, convertedConstant); + default: + throw new NotSupportedException($"{UnsupportedSearchTypeMessage} {property.SearchType}"); + } + } + + private bool IsNumericType(Type type, object value, out ConstantExpression convertedConstant) + { + convertedConstant = null; + if (NumericTypes.Contains(type)) + { + try + { + var convertedValue = Convert.ChangeType(Convert.ToString(value), type); + convertedConstant = Expression.Constant(convertedValue); + return true; + } + catch (InvalidCastException) + { + return false; + } + } + + return false; + } + + private bool IsDateTimeType(Type type, object value, out DateTime convertedDateTime) + { + convertedDateTime = DateTime.MinValue; + return type == typeof(DateTime) && DateTime.TryParse(Convert.ToString(value), out convertedDateTime); + } + + private static readonly HashSet NumericTypes = new HashSet + { + typeof(int), typeof(double), typeof(float), typeof(decimal), + typeof(long), typeof(short), typeof(byte), typeof(uint), + typeof(ulong), typeof(ushort), typeof(sbyte) + }; + } +} diff --git a/src/Modules/Shared/Dispo.Shared.Filter/Services/IFilterService.cs b/src/Modules/Shared/Dispo.Shared.Filter/Services/IFilterService.cs new file mode 100644 index 0000000..16ff635 --- /dev/null +++ b/src/Modules/Shared/Dispo.Shared.Filter/Services/IFilterService.cs @@ -0,0 +1,10 @@ +using Dispo.Shared.Core.Domain.Entities; +using Dispo.Shared.Filter.Model; + +namespace Dispo.Shared.Filter.Services +{ + public interface IFilterService + { + object Get(FilterModel filterModel) where T : EntityBase; + } +}