You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core Minimal API自动注册API端点的最佳实现方式

需求可行性

这个需求实现难度极低,完全基于.NET原生的反射、依赖注入、Minimal API原生能力就能实现,不需要修改现有业务基类的设计,整体代码量不到100行。

最佳实现方案

前置说明

所有EndpointBase<TRequest,TResponse>的非抽象实现类,需要先注册到依赖注入容器,推荐使用Scoped生命周期,和普通Minimal API的服务生命周期保持一致。
你现有示例里的UserEnpoint存在拼写错误,实际使用建议修正为UserEndpoint避免后续维护混淆。

完整实现代码

首先编写DI注册扩展方法,负责扫描指定程序集里所有符合要求的Endpoint类型:

public static class EndpointServiceCollectionExtensions
{
    private static readonly Type EndpointBaseOpenType = typeof(EndpointBase<,>);

    public static IServiceCollection AddEndpoints(this IServiceCollection services, params Assembly[] assembliesToScan)
    {
        // 未指定扫描程序集时,默认扫描应用入口程序集
        if (assembliesToScan.Length == 0)
        {
            assembliesToScan = new[] { Assembly.GetEntryAssembly()! };
        }

        // 查找所有非抽象、继承自EndpointBase<,>的实现类
        var endpointTypes = assembliesToScan
            .SelectMany(asm => asm.GetTypes())
            .Where(t => !t.IsAbstract && IsSubclassOfEndpointBase(t))
            .ToList();

        foreach (var endpointType in endpointTypes)
        {
            var closedBaseType = GetEndpointBaseClosedType(endpointType);
            // 以基类为服务类型注册Scoped生命周期,方便后续直接通过基类解析实例
            services.AddScoped(closedBaseType, endpointType);
        }

        // 缓存找到的Endpoint类型集合,避免后续重复反射扫描
        services.AddSingleton(endpointTypes);
        return services;
    }

    private static bool IsSubclassOfEndpointBase(Type type)
    {
        var current = type.BaseType;
        while (current != null)
        {
            if (current.IsGenericType && current.GetGenericTypeDefinition() == EndpointBaseOpenType)
                return true;
            current = current.BaseType;
        }
        return false;
    }

    private static Type GetEndpointBaseClosedType(Type endpointType)
    {
        var current = endpointType.BaseType;
        while (current != null)
        {
            if (current.IsGenericType && current.GetGenericTypeDefinition() == EndpointBaseOpenType)
                return current;
            current = current.BaseType;
        }
        throw new InvalidOperationException($"类型{endpointType.FullName}未继承自EndpointBase基类");
    }
}

然后实现你预期的UseEndpoints扩展方法,负责自动映射公开端点:

public static class EndpointApplicationExtensions
{
    private static readonly Type EndpointBaseOpenType = typeof(EndpointBase<,>);

    public static void UseEndpoints(this WebApplication app)
    {
        var endpointTypes = app.Services.GetRequiredService<List<Type>>();
        using var startupScope = app.Services.CreateScope();

        foreach (var endpointType in endpointTypes)
        {
            var closedBaseType = GetEndpointBaseClosedType(endpointType);
            // 启动阶段临时解析实例,读取端点配置
            var endpointInstance = startupScope.ServiceProvider.GetRequiredService(closedBaseType);
            
            // 非公开端点直接跳过
            var isPublic = (bool)closedBaseType.GetProperty(nameof(EndpointBase<object, object>.IsPublic))!
                .GetValue(endpointInstance)!;
            if (!isPublic) continue;

            // 读取路由配置
            var route = (string)closedBaseType.GetProperty(nameof(EndpointBase<object, object>.Route))!
                .GetValue(endpointInstance)!;

            // 反射调用泛型映射方法,避免手动构造表达式树
            var genericArgs = closedBaseType.GetGenericArguments();
            typeof(EndpointApplicationExtensions)
                .GetMethod(nameof(MapSingleEndpoint), BindingFlags.NonPublic | BindingFlags.Static)!
                .MakeGenericMethod(genericArgs)
                .Invoke(null, new object[] { app, route });
        }
    }

    private static void MapSingleEndpoint<TRequest, TResponse>(WebApplication app, string route)
        where TRequest : class, IRequest
        where TResponse : class
    {
        // 实际请求处理逻辑,每次请求从当前请求容器解析实例,符合Scoped生命周期
        app.MapGet(route, async (TRequest request, IServiceProvider sp) =>
        {
            var endpoint = sp.GetRequiredService<EndpointBase<TRequest, TResponse>>();
            return await endpoint.PerformAction(request);
        })
        .WithOpenApi(); // 需要集成Swagger则保留该行,不需要可删除
    }

    private static Type GetEndpointBaseClosedType(Type endpointType)
    {
        var current = endpointType.BaseType;
        while (current != null)
        {
            if (current.IsGenericType && current.GetGenericTypeDefinition() == EndpointBaseOpenType)
                return current;
            current = current.BaseType;
        }
        throw new InvalidOperationException($"类型{endpointType.FullName}未继承自EndpointBase基类");
    }
}

使用方式

在Program.cs中按顺序调用扩展方法即可:

var builder = WebApplication.CreateBuilder(args);
// 其他服务注册(如DbContext、Swagger等)
builder.Services.AddEndpoints();

var app = builder.Build();
// 其他中间件配置(如静态文件、认证授权中间件等)
app.UseEndpoints();

app.Run();

可选优化点

  • 目前实现默认映射为GET请求,如果你需要支持POST/PUT/DELETE等HTTP方法,可以给EndpointBase<TRequest,TResponse>加一个抽象的HttpMethod字符串属性,实现类返回HttpMethods.Get/HttpMethods.Post等对应值,注册的时候根据方法值调用MapGet/MapPost等对应映射方法即可。
  • 如果需要给端点加统一过滤器、异常处理、响应缓存策略,直接在MapSingleEndpoint方法的app.MapGet之后链式调用对应的Minimal API扩展方法即可,和手写端点的配置方式完全一致。
  • 对于非公开端点(IsPublic=false),你可以选择直接跳过映射,也可以映射后调用.RequireAuthorization()添加授权要求,根据自身业务需求调整即可。
  • TRequest参数的绑定规则和普通Minimal API参数完全一致:GET请求默认从QueryString绑定,POST请求默认从Body绑定,你也可以给TRequest类加[AsParameters]、[FromRoute]等特性自定义绑定来源。

内容的提问来源于stack exchange,提问作者Norbert Maziarz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 05:18:53