如何让Blazor共享项目的C#源代码生成器访问服务端编译上下文?
你遇到的这个问题在Blazor全栈开发里挺常见——想通过源码生成器自动把WebAPI的特性路由转成Shared项目里的URL生成方法,但Shared项目的生成器拿不到服务端的路由特性信息,硬编码解决方案路径又不够优雅。下面给你几个简洁的解决思路:
方案1:独立源码生成器项目 + 输出到Shared目录
这是最推荐的方案,既让生成器能访问服务端的编译上下文,又能让生成的代码自动进入Shared项目供两端使用:
- 创建独立的源码生成器类库:别把生成器放在Shared项目里,单独新建一个类库项目(比如
YourApp.ApiRouteGenerator),引用Roslyn必要包(Microsoft.CodeAnalysis.CSharp、Microsoft.CodeAnalysis.Analyzers等)。 - 服务端项目引用生成器并配置输出:在WebAPI项目的
.csproj里添加对生成器项目的引用,同时指定生成代码的输出目录为Shared项目的某个文件夹:
<ItemGroup> <ProjectReference Include="..\YourApp.ApiRouteGenerator\YourApp.ApiRouteGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /> </ItemGroup> <PropertyGroup> <!-- 把生成的代码输出到Shared项目的Generated文件夹,确保Shared项目包含这个目录 --> <GeneratedOutputDirectory>..\YourApp.Shared\Generated</GeneratedOutputDirectory> </PropertyGroup>
- 生成器逻辑与Shared项目配置:生成器里可以正常读取服务端项目的
[Route]、[HttpGet]等特性,生成URL方法后写入指定输出路径。最后在Shared项目的.csproj里添加<Compile Include="Generated/**/*.cs" />,让Shared自动包含生成的代码,客户端和服务端就能直接调用这些URL生成方法了。
这个方案完全利用Roslyn原生的编译上下文,不需要额外加载解决方案,既能保证生成逻辑的准确性,又能实现代码的跨端共享。
方案2:服务端+Shared共享路由常量(无生成器折中方案)
如果不想折腾独立生成器项目,这个手动维护的方案也能解决问题,适合路由变动不频繁的场景:
- 在Shared项目定义路由常量:创建一个静态类存储所有路由规则和名称:
public static class ApiRouteConstants { public const string ClientByIdRoute = "api/clients/{id}"; public const string ClientRouteName = "Client"; }
- 服务端引用常量配置路由:WebAPI的特性路由直接使用Shared里的常量:
[HttpGet] [Route(ApiRouteConstants.ClientByIdRoute, Name = ApiRouteConstants.ClientRouteName)] public async Task<ClientViewModel> GetById(int id) { var client = await _clientLogic.GetById(id); return _clientViewLogic.ToViewModel(client); }
- Shared里编写URL生成方法:基于同一个常量类实现URL拼接:
public static string GetClientUrl(string id) { return ApiRouteConstants.ClientByIdRoute.Replace("{id}", id); }
这种方式不需要生成器,通过共享常量保证路由规则的一致性,手动维护成本低。
方案3:通过MSBuild属性传递解决方案路径(不推荐,相对复杂)
如果坚持要用Shared项目里的生成器,可以通过MSBuild把解决方案路径传递给生成器,避免硬编码:
- 服务端项目传递路径:在WebAPI项目的
.csproj里添加:
<PropertyGroup> <SolutionPath>$(SolutionDir)</SolutionPath> </PropertyGroup>
- 生成器获取路径并加载工作区:在生成器里通过上下文获取路径,加载解决方案:
public void Execute(GeneratorExecutionContext context) { if (context.AnalyzerConfigOptions.GlobalOptions.TryGetValue("build_property.SolutionPath", out var solutionPath)) { var workspace = MSBuildWorkspace.Create(); var solution = workspace.OpenSolutionAsync(solutionPath).Result; // 遍历服务端项目的编译单元,获取路由特性信息 } }
不过这个方案需要处理异步加载的异常情况,实现复杂度高,除非有特殊需求不推荐使用。
内容的提问来源于stack exchange,提问作者cpaulus
相关产品推荐
相关产品推荐

