Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

实现IQuery+IQueryHandler模式 #99

Merged
merged 1 commit into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/AspNetCore/IPagedQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
锘縰sing 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<TResponse> : IQuery<PagedData<TResponse>>
{
public int PageIndex { get; }
public int PageSize { get; }
public bool CountTotal { get; }
}
}
29 changes: 22 additions & 7 deletions src/AspNetCore/PageQueryableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ public static class PageQueryableExtensions
{
public static async Task<PagedData<T>> ToPagedDataAsync<T>(
this IQueryable<T> 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涓簍rue鏃舵墠鏌ヨ鎬绘暟銆傞粯璁や笉闇�瑕佹�绘暟
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)
Expand All @@ -25,4 +32,12 @@ public static async Task<PagedData<T>> ToPagedDataAsync<T>(
return new PagedData<T>(items, totalCount, pageIndex, pageSize);
}


public static Task<PagedData<T>> ToPagedDataAsync<T>(
this IQueryable<T> query,
IPagedQuery<T> pagedQuery,
CancellationToken cancellationToken = default)
{
return query.ToPagedDataAsync(pagedQuery.PageIndex, pagedQuery.PageSize, pagedQuery.CountTotal, cancellationToken);
}
}
26 changes: 26 additions & 0 deletions src/AspNetCore/ServiceCollectionExtension.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -25,4 +27,28 @@
cfg.AddOpenBehavior(typeof(KnownExceptionValidationBehavior<,>));
return cfg;
}



/// <summary>
/// 将所有实现IQuery接口的类注册为查询类,添加到容器中
/// </summary>
/// <param name="services"></param>
/// <param name="Assemblies"></param>
/// <returns></returns>

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

Check warning on line 45 in src/AspNetCore/ServiceCollectionExtension.cs

View workflow job for this annotation

GitHub Actions / build

Collection-specific "Exists" method should be used instead of the "Any" extension. (https://rules.sonarsource.com/csharp/RSPEC-6605)
foreach (var queryType in queryTypes)
{
//注册为自己
services.AddTransient(queryType, queryType);
}
}
return services;
}
}
23 changes: 23 additions & 0 deletions src/Primitives/IQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
锘縰sing MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NetCorePal.Extensions.Primitives
{


/// <summary>
/// 琛ㄧず涓�涓被涓烘煡璇㈢被锛屾鏋跺垯浼氳嚜鍔ㄦ敞鍐岃绫绘煡璇㈢被
/// </summary>
public interface IQuery;


/// <summary>
/// 琛ㄧず涓�涓被涓烘煡璇㈢被锛屽苟鎸囧畾鏌ヨ缁撴灉绫诲瀷锛岃Query鐢盦ueryHandler澶勭悊骞剁浉搴�
/// </summary>
/// <typeparam name="TResponse"></typeparam>
public interface IQuery<out TResponse> : IRequest<TResponse>;
}
16 changes: 16 additions & 0 deletions src/Primitives/IQueryHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
锘縰sing MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NetCorePal.Extensions.Primitives;

public interface IQueryHandler<in TQuery, TResponse> : IRequestHandler<TQuery, TResponse>
where TQuery : IQuery<TResponse>
{
}



Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
锘�<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
Expand All @@ -23,6 +23,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\AspNetCore\NetCorePal.Extensions.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\Context.AspNetCore\NetCorePal.Context.AspNetCore.csproj" />
<ProjectReference Include="..\..\src\Context.Shared\NetCorePal.Context.Shared.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
锘縰sing 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);

Check warning on line 25 in test/NetCorePal.Context.AspNetCore.UnitTests/ServiceCollectionExtensionTests.cs

View workflow job for this annotation

GitHub Actions / build

Do not use Assert.Equal() to check for collection size. Use Assert.Single instead. (https://xunit.net/xunit.analyzers/rules/xUnit2013)
var provider = services.BuildServiceProvider();
var queryHandler = provider.GetRequiredService<Query1>();
Assert.NotNull(queryHandler);
}
}
}
23 changes: 22 additions & 1 deletion test/NetCorePal.Web.UnitTests/ProgramTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrderId>(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<ResponseData<PagedData<GetOrderByNameDto>>>(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()
{
Expand Down Expand Up @@ -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]
Expand Down
22 changes: 22 additions & 0 deletions test/NetCorePal.Web/Application/Queries/GetOrderByNameQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
锘縰sing 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<GetOrderByNameDto>;


public class GetOrderByNameQueryHandler(ApplicationDbContext dbContext) : IQueryHandler<GetOrderByNameQuery, PagedData<GetOrderByNameDto>>
{
public async Task<PagedData<GetOrderByNameDto>> 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);
}
}
}
9 changes: 9 additions & 0 deletions test/NetCorePal.Web/Controllers/OrderController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,15 @@ public Task<ResponseData<OrderId>> Path([FromRoute] OrderId id)
{
return Task.FromResult(new ResponseData<OrderId>(id));
}


[HttpGet]
[Route("/query/orderbyname")]
public async Task<ResponseData<PagedData<GetOrderByNameDto>>> QueryOrderByName([FromQuery] GetOrderByNameQuery query)
{
var data = await mediator.Send(query, HttpContext.RequestAborted);
return data.AsResponseData();
}
}

/// <summary>
Expand Down
Loading