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

Blazor项目中如何避免为各实体实现空泛型控制器?

解决Blazor项目中泛型控制器的冗余空控制器问题

你可以通过两种方案消除手动编写空控制器的冗余工作,以下是具体实现:


方案一:运行时动态注册泛型控制器

通过自定义控制器提供者,扫描程序集中的实体类型,自动为每组实体/Identity/Filter创建泛型控制器实例并注册到MVC路由系统。

1. 实现自定义控制器特征提供者

using Microsoft.AspNetCore.Mvc.Controllers;
using System.Reflection;

public class GenericControllerFeatureProvider : ControllerFeatureProvider
{
    private readonly Type _genericControllerType = typeof(GenericController<,,>);
    private readonly Assembly _entitiesAssembly;
    private readonly Assembly _servicesAssembly;

    public GenericControllerFeatureProvider(Assembly entitiesAssembly, Assembly servicesAssembly)
    {
        _entitiesAssembly = entitiesAssembly;
        _servicesAssembly = servicesAssembly;
    }

    protected override bool IsController(TypeInfo typeInfo)
    {
        // 保留原有控制器的识别逻辑
        if (base.IsController(typeInfo))
            return true;

        // 扫描所有非抽象、非泛型的实体类型
        foreach (var entityType in _entitiesAssembly.GetTypes().Where(t => !t.IsAbstract && !t.IsGenericType))
        {
            // 匹配对应的Identity类型(按[实体名]Identity命名规则)
            var identityType = _entitiesAssembly.GetType($"{entityType.Namespace}.{entityType.Name}Identity");
            if (identityType == null || !typeof(IIdentity).IsAssignableFrom(identityType))
                continue;

            // 匹配对应的Filter类型(按[实体名]Filter命名规则)
            var filterType = _entitiesAssembly.GetType($"{entityType.Namespace}.{entityType.Name}Filter");
            if (filterType == null)
                continue;

            // 匹配对应的服务接口(按I[实体名]Service命名规则)
            var serviceInterfaceType = _servicesAssembly.GetType($"{entityType.Namespace}.I{entityType.Name}Service");
            var genericServiceType = typeof(IGenericService<,,>).MakeGenericType(entityType, identityType, filterType);
            if (serviceInterfaceType == null || !genericServiceType.IsAssignableFrom(serviceInterfaceType))
                continue;

            // 创建泛型控制器的具体类型
            var controllerType = _genericControllerType.MakeGenericType(entityType, identityType, filterType);
            
            // 添加路由、ApiController、授权等特性
            controllerType.GetTypeInfo().SetCustomAttribute(new RouteAttribute($"api/[controller]"));
            controllerType.GetTypeInfo().SetCustomAttribute(new ApiControllerAttribute());
            controllerType.GetTypeInfo().SetCustomAttribute(new AuthorizeAttribute());

            // 将类型注册为控制器
            ControllerFeature.Controllers.Add(controllerType.GetTypeInfo());
        }

        return false;
    }
}

2. 在Program.cs中注册提供者

var builder = WebApplication.CreateBuilder(args);

// 替换默认控制器提供者,注册自定义实现
builder.Services.AddControllers()
    .ConfigureApplicationPartManager(apm =>
    {
        var defaultProvider = apm.FeatureProviders.OfType<ControllerFeatureProvider>().First();
        apm.FeatureProviders.Remove(defaultProvider);
        apm.FeatureProviders.Add(new GenericControllerFeatureProvider(
            Assembly.GetAssembly(typeof(ErrorCode)),    // 实体所在程序集
            Assembly.GetAssembly(typeof(IErrorCodeService)) // 服务所在程序集
        ));
    });

// 其他服务配置...

var app = builder.Build();
app.MapControllers();
app.Run();

方案二:编译时源代码生成器

通过Roslyn源代码生成器,在编译阶段自动为每个实体生成对应的空控制器代码,既保留静态代码的可调试性,又无需手动编写。

1. 实现源代码生成器

创建一个.NET Standard类库项目,引用Microsoft.CodeAnalysis.CSharp和Microsoft.CodeAnalysis.Analyzers NuGet包,添加以下代码:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using System.Linq;
using System.Text;

[Generator]
public class GenericControllerGenerator : ISourceGenerator
{
    public void Initialize(GeneratorInitializationContext context)
    {
        context.RegisterForSyntaxNotifications(() => new EntitySyntaxReceiver());
    }

    public void Execute(GeneratorExecutionContext context)
    {
        if (!(context.SyntaxReceiver is EntitySyntaxReceiver receiver))
            return;

        foreach (var entityType in receiver.EntityTypes)
        {
            var model = context.Compilation.GetSemanticModel(entityType.SyntaxTree);
            var symbol = model.GetDeclaredSymbol(entityType) as INamedTypeSymbol;
            if (symbol == null || symbol.IsAbstract || symbol.IsGenericType)
                continue;

            var entityName = symbol.Name;
            var namespaceName = symbol.ContainingNamespace.ToDisplayString();

            // 生成控制器代码
            var source = $@"
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace {namespaceName};

[Authorize]
[Route(""api/[controller]"")]
[ApiController]
public class {entityName}Controller : GenericController<{entityName}, {entityName}Identity, {entityName}Filter>
{{
    public {entityName}Controller(I{entityName}Service service, ILogger<{entityName}Controller> logger) 
        : base(service, logger)
    {{
    }}
}}";

            context.AddSource($"{entityName}Controller.g.cs", source);
        }
    }

    private class EntitySyntaxReceiver : ISyntaxReceiver
    {
        public List<ClassDeclarationSyntax> EntityTypes { get; } = new();

        public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
        {
            if (syntaxNode is ClassDeclarationSyntax classDecl && !classDecl.Modifiers.Any(SyntaxKind.AbstractKeyword))
            {
                EntityTypes.Add(classDecl);
            }
        }
    }
}

2. 在主项目中引用生成器

在Blazor服务端项目的csproj中添加以下引用:

<ItemGroup>
    <ProjectReference Include="..\路径\到\生成器项目\GenericControllerGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

编译主项目时,生成器会自动为每个实体生成对应的空控制器代码。


方案对比

方案类型优点缺点
动态注册无额外代码文件、实体变动无需重新编译调试时无法直接查看控制器代码
源代码生成器生成真实代码、调试友好需要维护生成器逻辑、实体变动需重新编译

注意事项

  • 确保实体、Identity、Filter、服务的命名规则统一,若规则不同需调整代码中的类型查找逻辑。
  • 如需为特定实体配置不同的授权或路由规则,可在动态注册或生成器中添加条件判断逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:23:13