diff --git a/src/AspNetCore/IPagedQuery.cs b/src/AspNetCore/IPagedQuery.cs new file mode 100644 index 0000000..442dc2e --- /dev/null +++ b/src/AspNetCore/IPagedQuery.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.Extensions.AspNetCore +{ + public interface IPagedQuery : IQuery> + { + public int PageIndex { get; } + public int PageSize { get; } + public bool CountTotal { get; } + } +} diff --git a/src/AspNetCore/PageQueryableExtensions.cs b/src/AspNetCore/PageQueryableExtensions.cs index 7d72172..f0a13d1 100644 --- a/src/AspNetCore/PageQueryableExtensions.cs +++ b/src/AspNetCore/PageQueryableExtensions.cs @@ -7,16 +7,23 @@ public static class PageQueryableExtensions { public static async Task> ToPagedDataAsync( this IQueryable query, - int? index, - int? size, - bool? countTotal, - CancellationToken cancellationToken) + int pageIndex = 1, + int pageSize = 10, + bool countTotal = false, + CancellationToken cancellationToken = default) { - var pageIndex = index ?? 1; // 默认取第1页 - var pageSize = size ?? 10; // 默认每页10条 + if (pageIndex <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageIndex), "页码必须大于 0"); + } + + if (pageSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(pageSize), "每页条数必须大于 0"); + } // isTotalNeeded为true时才查询总数。默认不需要总数 - var totalCount = countTotal ?? false ? await query.CountAsync(cancellationToken) : 0; + var totalCount = countTotal ? await query.CountAsync(cancellationToken) : 0; var items = await query.Skip((pageIndex - 1) * pageSize) .Take(pageSize) @@ -25,4 +32,12 @@ public static async Task> ToPagedDataAsync( return new PagedData(items, totalCount, pageIndex, pageSize); } + + public static Task> ToPagedDataAsync( + this IQueryable query, + IPagedQuery pagedQuery, + CancellationToken cancellationToken = default) + { + return query.ToPagedDataAsync(pagedQuery.PageIndex, pagedQuery.PageSize, pagedQuery.CountTotal, cancellationToken); + } } diff --git a/src/AspNetCore/ServiceCollectionExtension.cs b/src/AspNetCore/ServiceCollectionExtension.cs index ce00402..c7cdbdb 100644 --- a/src/AspNetCore/ServiceCollectionExtension.cs +++ b/src/AspNetCore/ServiceCollectionExtension.cs @@ -1,7 +1,9 @@ +using System.Reflection; using FluentValidation.AspNetCore; using Microsoft.Extensions.DependencyInjection; using NetCorePal.Extensions.AspNetCore; using NetCorePal.Extensions.AspNetCore.Validation; +using NetCorePal.Extensions.Primitives; namespace NetCorePal.Extensions.DependencyInjection; @@ -25,4 +27,28 @@ public static MediatRServiceConfiguration AddKnownExceptionValidationBehavior( cfg.AddOpenBehavior(typeof(KnownExceptionValidationBehavior<,>)); return cfg; } + + + + /// + /// ʵIQueryӿڵעΪѯ࣬ӵ + /// + /// + /// + /// + + public static IServiceCollection AddAllQueries(this IServiceCollection services, params Assembly[] Assemblies) + { + foreach (var assembly in Assemblies) + { + //assemblyлȡʵIQueryӿڵ + var queryTypes = assembly.GetTypes().Where(p => p.IsClass && !p.IsAbstract && p.GetInterfaces().Any(i => i == typeof(IQuery))); + foreach (var queryType in queryTypes) + { + //עΪԼ + services.AddTransient(queryType, queryType); + } + } + return services; + } } \ No newline at end of file diff --git a/src/Primitives/IQuery.cs b/src/Primitives/IQuery.cs new file mode 100644 index 0000000..0751bb3 --- /dev/null +++ b/src/Primitives/IQuery.cs @@ -0,0 +1,23 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.Extensions.Primitives +{ + + + /// + /// 表示一个类为查询类,框架则会自动注册该类查询类 + /// + public interface IQuery; + + + /// + /// 表示一个类为查询类,并指定查询结果类型,该Query由QueryHandler处理并相应 + /// + /// + public interface IQuery : IRequest; +} diff --git a/src/Primitives/IQueryHandler.cs b/src/Primitives/IQueryHandler.cs new file mode 100644 index 0000000..b92a0ed --- /dev/null +++ b/src/Primitives/IQueryHandler.cs @@ -0,0 +1,16 @@ +using MediatR; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetCorePal.Extensions.Primitives; + +public interface IQueryHandler : IRequestHandler + where TQuery : IQuery +{ +} + + + diff --git a/test/NetCorePal.Context.AspNetCore.UnitTests/NetCorePal.Context.AspNetCore.UnitTests.csproj b/test/NetCorePal.Context.AspNetCore.UnitTests/NetCorePal.Context.AspNetCore.UnitTests.csproj index 9815298..821ec92 100644 --- a/test/NetCorePal.Context.AspNetCore.UnitTests/NetCorePal.Context.AspNetCore.UnitTests.csproj +++ b/test/NetCorePal.Context.AspNetCore.UnitTests/NetCorePal.Context.AspNetCore.UnitTests.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0 @@ -23,6 +23,7 @@ + diff --git a/test/NetCorePal.Context.AspNetCore.UnitTests/ServiceCollectionExtensionTests.cs b/test/NetCorePal.Context.AspNetCore.UnitTests/ServiceCollectionExtensionTests.cs new file mode 100644 index 0000000..434ba41 --- /dev/null +++ b/test/NetCorePal.Context.AspNetCore.UnitTests/ServiceCollectionExtensionTests.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NetCorePal.Extensions.DependencyInjection; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.Context.AspNetCore.UnitTests +{ + + public class Query1 : IQuery + { + } + + + public class ServiceCollectionExtensionTests + { + [Fact] + public void AddAllQueriesTests() + { + var services = new ServiceCollection(); + services.AddAllQueries(typeof(ServiceCollectionExtensionTests).Assembly); + Assert.Equal(1, services.Count); + var provider = services.BuildServiceProvider(); + var queryHandler = provider.GetRequiredService(); + Assert.NotNull(queryHandler); + } + } +} diff --git a/test/NetCorePal.Web.UnitTests/ProgramTests.cs b/test/NetCorePal.Web.UnitTests/ProgramTests.cs index 2bac7c0..9fb160b 100644 --- a/test/NetCorePal.Web.UnitTests/ProgramTests.cs +++ b/test/NetCorePal.Web.UnitTests/ProgramTests.cs @@ -136,6 +136,27 @@ public async Task PostTest() } + [Fact] + public async Task QueryOrderByNameTest() + { + var client = factory.CreateClient(); + var response = await client.PostAsJsonAsync("/api/order", new CreateOrderRequest("na2", 55, 14), JsonOption); + Assert.True(response.IsSuccessStatusCode); + var r = await response.Content.ReadFromJsonAsync(JsonOption); + Assert.NotNull(r); + + + client = factory.CreateClient(); + response = await client.GetAsync("/query/orderbyname?name=na2"); + Assert.True(response.IsSuccessStatusCode); + var data = await response.Content.ReadFromJsonAsync>>(JsonOption); + Assert.NotNull(data); + Assert.True(data.Success); + Assert.Single(data.Data.Items); + Assert.Equal("na2", data.Data.Items.First().Name); + } + + [Fact] public async Task SetPaidTest() { @@ -196,7 +217,7 @@ public async Task SetOrderItemNameTest() Assert.False(queryResult.Paid); Assert.Equal(1, queryResult.RowVersion.VersionNumber); Assert.Single(queryResult.OrderItems); - Assert.Equal(1,queryResult.OrderItems.First().RowVersion.VersionNumber); + Assert.Equal(1, queryResult.OrderItems.First().RowVersion.VersionNumber); } [Fact] diff --git a/test/NetCorePal.Web/Application/Queries/GetOrderByNameQuery.cs b/test/NetCorePal.Web/Application/Queries/GetOrderByNameQuery.cs new file mode 100644 index 0000000..f536f9a --- /dev/null +++ b/test/NetCorePal.Web/Application/Queries/GetOrderByNameQuery.cs @@ -0,0 +1,22 @@ +using NetCorePal.Extensions.Dto; +using NetCorePal.Extensions.Primitives; + +namespace NetCorePal.Web.Application.Queries +{ + + + public record GetOrderByNameDto(OrderId OrderId, string Name); + + public record GetOrderByNameQuery(string Name, int PageIndex = 1, int PageSize = 10, bool CountTotal = false) : IPagedQuery; + + + public class GetOrderByNameQueryHandler(ApplicationDbContext dbContext) : IQueryHandler> + { + public async Task> Handle(GetOrderByNameQuery request, CancellationToken cancellationToken) + { + return await dbContext.Orders.Where(x => x.Name.Contains(request.Name)) + .Select(p => new GetOrderByNameDto(p.Id, p.Name)) + .ToPagedDataAsync(request, cancellationToken); + } + } +} diff --git a/test/NetCorePal.Web/Controllers/OrderController.cs b/test/NetCorePal.Web/Controllers/OrderController.cs index 0950e73..0ef9b37 100644 --- a/test/NetCorePal.Web/Controllers/OrderController.cs +++ b/test/NetCorePal.Web/Controllers/OrderController.cs @@ -220,6 +220,15 @@ public Task> Path([FromRoute] OrderId id) { return Task.FromResult(new ResponseData(id)); } + + + [HttpGet] + [Route("/query/orderbyname")] + public async Task>> QueryOrderByName([FromQuery] GetOrderByNameQuery query) + { + var data = await mediator.Send(query, HttpContext.RequestAborted); + return data.AsResponseData(); + } } ///