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
相关产品推荐
相关产品推荐

