AvaloniaStack/Avalonia-API/Extensions/UnifiedEndpointExtensions.cs
luoqian fc6f9f6bc3 docs: 补全全部缺失的 XML 文档注释(中文)
- 为全部 5 个项目(Avalonia-API、Avalonia-Common、Avalonia-EFCore、
  Avalonia-PC、Avalonia-Services)中缺失注释的类、方法、属性、字段、
  接口成员等补全中文 XML 文档注释
- 共修改约 37 个文件,补全约 220+ 处注释
- 修复 ServiceEndpointCollection.cs 中 MapDelete<TService> 语法错误
- 修复 PcAuthService.cs 中 const prefix 位置错乱导致编译失败的问题
- 扫描结果:缺失项 0
- 构建结果:4/4 项目编译通过
2026-05-18 11:35:13 +08:00

226 lines
9.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Avalonia_Services.Core;
using Microsoft.AspNetCore.Authorization;
using AspNetCoreFilterContext = Microsoft.AspNetCore.Http.EndpointFilterInvocationContext;
using AspNetCoreFilterDelegate = Microsoft.AspNetCore.Http.EndpointFilterDelegate;
// 解决与 ASP.NET Core 同名类型的冲突
using UnifiedFilter = Avalonia_Services.Core.IEndpointFilter;
namespace Avalonia_API.Extensions
{
/// <summary>
/// 将 Avalonia-Services 的统一端点映射到 ASP.NET Core Minimal API。
/// 支持鉴权、过滤器、中间件的完整 ASP.NET Core 管道。
/// </summary>
public static class UnifiedEndpointExtensions
{
/// <summary>
/// 将 ServiceEndpointCollection 中的所有端点注册到 ASP.NET Core 路由。
/// </summary>
public static IEndpointRouteBuilder MapUnifiedEndpoints(
this IEndpointRouteBuilder routeBuilder,
ServiceEndpointCollection endpoints,
IServiceProvider serviceProvider)
{
var apiGroup = routeBuilder.MapGroup("/");
foreach (var endpoint in endpoints.ForHost(EndpointHostTarget.Api))
{
var routeHandlerBuilder = MapEndpoint(apiGroup, endpoint, serviceProvider);
// 全局过滤器 → ASP.NET Core Endpoint Filters
foreach (var globalFilter in endpoints.GlobalFilters)
{
routeHandlerBuilder.AddEndpointFilter(
async (context, next) => await ConvertFilterAsync(globalFilter, context, next));
}
// 端点专属过滤器
foreach (var filter in endpoint.Filters)
{
routeHandlerBuilder.AddEndpointFilter(
async (context, next) => await ConvertFilterAsync(filter, context, next));
}
// 鉴权(使用 ASP.NET Core 原生鉴权机制)
if (endpoint.RequireAuthorization)
{
if (endpoint.Roles.Count > 0)
{
routeHandlerBuilder.RequireAuthorization(new AuthorizeAttribute
{
Roles = string.Join(',', endpoint.Roles),
});
}
else if (!string.IsNullOrEmpty(endpoint.Policy))
{
routeHandlerBuilder.RequireAuthorization(endpoint.Policy);
}
else
{
routeHandlerBuilder.RequireAuthorization();
}
}
if (!string.IsNullOrEmpty(endpoint.Name))
{
routeHandlerBuilder.WithName(endpoint.Name);
}
if (!string.IsNullOrEmpty(endpoint.OpenApiTag))
{
routeHandlerBuilder.WithTags(endpoint.OpenApiTag);
}
if (!string.IsNullOrEmpty(endpoint.OpenApiDescription))
{
routeHandlerBuilder.WithDescription(endpoint.OpenApiDescription);
}
if (!string.IsNullOrWhiteSpace(endpoint.OpenApiSummary))
{
routeHandlerBuilder.WithSummary(endpoint.OpenApiSummary);
}
if (endpoint.OpenApiRequestType is not null)
{
routeHandlerBuilder.Accepts(endpoint.OpenApiRequestType, "application/json");
}
if (endpoint.OpenApiResponseType is not null)
{
routeHandlerBuilder.Produces(200, endpoint.OpenApiResponseType, "application/json");
}
}
return routeBuilder;
}
/// <summary>
/// 根据端点的 HTTP 方法GET/POST/PUT/DELETE将其映射到 ASP.NET Core 路由。
/// </summary>
/// <param name="group">路由组。</param>
/// <param name="endpoint">统一端点定义。</param>
/// <param name="serviceProvider">服务提供程序。</param>
/// <returns>路由处理器构建器,用于叠加过滤器等配置。</returns>
private static RouteHandlerBuilder MapEndpoint(
IEndpointRouteBuilder group,
ServiceEndpoint endpoint,
IServiceProvider serviceProvider)
{
var handler = CreateAspNetCoreHandler(endpoint.Handler, serviceProvider);
return endpoint.HttpMethod.ToUpperInvariant() switch
{
"GET" => group.MapGet(endpoint.Pattern, handler),
"POST" => group.MapPost(endpoint.Pattern, handler),
"PUT" => group.MapPut(endpoint.Pattern, handler),
"DELETE" => group.MapDelete(endpoint.Pattern, handler),
_ => group.MapGet(endpoint.Pattern, handler),
};
}
/// <summary>
/// 创建适配 ASP.NET Core 的委托处理器,将统一处理器包装为 ASP.NET Core 可识别的委托。
/// </summary>
/// <param name="unifiedHandler">统一端点处理器。</param>
/// <param name="serviceProvider">服务提供程序。</param>
/// <returns>ASP.NET Core 兼容的委托。</returns>
private static Delegate CreateAspNetCoreHandler(
Func<ServiceEndpointContext, Task<object?>> unifiedHandler,
IServiceProvider serviceProvider)
{
return async (HttpContext httpContext) =>
{
var ctx = await BuildContextFromHttpContext(httpContext);
ctx.Items["ServiceProvider"] = serviceProvider;
ctx.Items["User"] = httpContext.User;
var result = await unifiedHandler(ctx);
// 同步响应状态
httpContext.Response.StatusCode = ctx.StatusCode;
foreach (var kvp in ctx.ResponseHeaders)
{
httpContext.Response.Headers[kvp.Key] = kvp.Value;
}
return result is not null ? Results.Json(result) : Results.Ok();
};
}
/// <summary>
/// 从 ASP.NET Core 的 HttpContext 构建统一的 ServiceEndpointContext
/// 提取路径、方法、请求头、查询参数和请求体。
/// </summary>
/// <param name="httpContext">ASP.NET Core 的 HttpContext。</param>
/// <returns>构建好的统一端点上下文。</returns>
private static async Task<ServiceEndpointContext> BuildContextFromHttpContext(HttpContext httpContext)
{
var ctx = new ServiceEndpointContext
{
Path = httpContext.Request.Path.Value ?? "/",
Method = httpContext.Request.Method,
StatusCode = 200,
};
foreach (var header in httpContext.Request.Headers)
{
ctx.Headers[header.Key] = header.Value.ToString();
}
foreach (var query in httpContext.Request.Query)
{
ctx.Query[query.Key] = query.Value.ToString();
}
if (httpContext.Request.ContentLength > 0)
{
using var reader = new StreamReader(httpContext.Request.Body);
ctx.Body = await reader.ReadToEndAsync();
}
ctx.Items["HttpContext"] = httpContext;
ctx.Items["User"] = httpContext.User;
return ctx;
}
/// <summary>
/// 将统一过滤器转换为 ASP.NET Core 端点过滤器,
/// 在调用统一过滤器前后桥接上下文和状态。
/// </summary>
/// <param name="unifiedFilter">统一过滤器。</param>
/// <param name="aspContext">ASP.NET Core 过滤器调用上下文。</param>
/// <param name="aspNext">ASP.NET Core 过滤器管道中的下一个委托。</param>
/// <returns>过滤器执行结果,可能包含短路响应体。</returns>
private static async ValueTask<object?> ConvertFilterAsync(
UnifiedFilter unifiedFilter,
AspNetCoreFilterContext aspContext,
AspNetCoreFilterDelegate aspNext)
{
var httpContext = aspContext.HttpContext;
var ctx = httpContext.Items["UnifiedContext"] as ServiceEndpointContext
?? await BuildContextFromHttpContext(httpContext);
httpContext.Items["UnifiedContext"] = ctx;
await unifiedFilter.InvokeAsync(ctx, async (c) =>
{
httpContext.Response.StatusCode = c.StatusCode;
foreach (var kvp in c.ResponseHeaders)
{
httpContext.Response.Headers[kvp.Key] = kvp.Value;
}
await aspNext(aspContext);
});
if (ctx.ResponseBody is not null)
{
return Results.Json(ctx.ResponseBody, statusCode: ctx.StatusCode);
}
return null!;
}
}
}