- A+
前面学习了一些Source Generators的基础只是,接下来就来实践一下,用这个来生成我们所需要的代码。
本文将通过读取swagger.json的内容,解析并生成对应的请求响应类的代码。
创建项目
首先还是先创建两个项目,一个控制台程序,一个类库。
添加swagger文件
在控制台程序中添加Files目录,并把swagger文件放进去。别忘了还需要添加AdditionalFiles。
<ItemGroup> <AdditionalFiles Include="Filesswagger.json" /> </ItemGroup>
实现ClassFromSwaggerGenerator
安装依赖
由于我们需要解析swagger,所以需要安装一下JSON相关的包。这里我们安装了Newtonsoft.Json。
需要注意的是,依赖第三方包的时候需要在项目文件添加下面内容:
<PropertyGroup> <GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn> </PropertyGroup> <Target Name="GetDependencyTargetPaths" AfterTargets="ResolvePackageDependenciesForBuild"> <ItemGroup> <TargetPathWithTargetPlatformMoniker Include="@(ResolvedCompileFileDefinitions)" IncludeRuntimeDependency="false" /> </ItemGroup> </Target>
否则编译时会出现FileNotFound的异常。
构建管道
这里我们通过AdditionalTextsProvider筛选以及过滤我们的swagger文件。
var pipeline = context.AdditionalTextsProvider.Select(static (text, cancellationToken) => { if (!text.Path.EndsWith("swagger.json", StringComparison.OrdinalIgnoreCase)) { return default; } return JObject.Parse(text.GetText(cancellationToken)!.ToString()); }) .Where((pair) => pair is not null);
实现生成代码逻辑
接下来我们就解析Swagger中的内容,并且动态拼接代码内容。主要代码部分如下:
context.RegisterSourceOutput(pipeline, static (context, swagger) => { List<(string name, string sourceString)> sources = new List<(string name, string sourceString)>(); #region 生成实体 var schemas = (JObject)swagger["components"]!["schemas"]!; foreach (JProperty item in schemas.Properties()) { if (item != null) { sources.Add((HandleClassName(item.Name), $@"#nullable enable using System; using System.Collections.Generic; namespace SwaggerEntities; public {ClassOrEnum((JObject)item.Value)} {HandleClassName(item.Name)} {{ {BuildProperty((JObject)item.Value)} }} ")); } } foreach (var (name, sourceString) in sources) { var sourceText = SourceText.From(sourceString, Encoding.UTF8); context.AddSource($"{name}.g.cs", sourceText); } #endregion });
完整的代码如下:
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; namespace GenerateClassFromSwagger.Analysis { [Generator] public class ClassFromSwaggerGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { var pipeline = context.AdditionalTextsProvider.Select(static (text, cancellationToken) => { if (!text.Path.EndsWith("swagger.json", StringComparison.OrdinalIgnoreCase)) { return default; } return JObject.Parse(text.GetText(cancellationToken)!.ToString()); }) .Where((pair) => pair is not null); context.RegisterSourceOutput(pipeline, static (context, swagger) => { List<(string name, string sourceString)> sources = new List<(string name, string sourceString)>(); #region 生成实体 var schemas = (JObject)swagger["components"]!["schemas"]!; foreach (JProperty item in schemas.Properties()) { if (item != null) { sources.Add((HandleClassName(item.Name), $@"#nullable enable using System; using System.Collections.Generic; namespace SwaggerEntities; public {ClassOrEnum((JObject)item.Value)} {HandleClassName(item.Name)} {{ {BuildProperty((JObject)item.Value)} }} ")); } } foreach (var (name, sourceString) in sources) { var sourceText = SourceText.From(sourceString, Encoding.UTF8); context.AddSource($"{name}.g.cs", sourceText); } #endregion }); } static string HandleClassName(string name) { return name.Split('.').Last().Replace("<", "").Replace(">", "").Replace(",", ""); } static string ClassOrEnum(JObject value) { return value.ContainsKey("enum") ? "enum" : "partial class"; } static string BuildProperty(JObject value) { var sb = new StringBuilder(); if (value.ContainsKey("properties")) { var propertys = (JObject)value["properties"]!; foreach (JProperty item in propertys!.Properties()) { sb.AppendLine($@" public {BuildProertyType((JObject)item.Value)} {ToUpperFirst(item.Name)} {{ get; set; }} "); } } if (value.ContainsKey("enum")) { foreach (var item in JsonConvert.DeserializeObject<List<int>>(value["enum"]!.ToString())!) { sb.Append($@" _{item}, "); } sb.Remove(sb.Length - 1, 1); } return sb.ToString(); } static string BuildProertyType(JObject value) { ; var type = GetType(value); var nullable = value.ContainsKey("nullable") ? value["nullable"]!.Value<bool?>() switch { true => "?", false => "", _ => "" } : ""; return type + nullable; } static string GetType(JObject value) { return value.ContainsKey("type") ? value["type"]!.Value<string>() switch { "string" => "string", "boolean" => "bool", "number" => value["format"]!.Value<string>() == "float" ? "float" : "double", "integer" => value["format"]!.Value<string>() == "int32" ? "int" : "long", "array" => ((JObject)value["items"]!).ContainsKey("items") ? $"List<{HandleClassName(value["items"]!["$ref"]!.Value<string>()!)}>" : $"List<{GetType((JObject)value["items"]!)}>", "object" => value.ContainsKey("additionalProperties") ? $"Dictionary<string, {GetType((JObject)value["additionalProperties"]!)}>" : "object", _ => "object" } : value.ContainsKey("$ref") ? HandleClassName(value["$ref"]!.Value<string>()!) : "object"; } static unsafe string ToUpperFirst(string str) { if (str == null) return null; string ret = string.Copy(str); fixed (char* ptr = ret) *ptr = char.ToUpper(*ptr); return ret; } } }
详细的处理过程大家可以仔细看看代码,这里就不一一解释了。
启动编译
接下来编译控制台程序。编译成功后可以看到生成了很多cs的代码。若是看不见,可以重启VS。
点开一个文件,可以看到内容,并且在上方提示自动生成,无法编辑。
到这我们就完成了通过swagger来生成我们的请求和响应类的功能。
结语
本文章应用SourceGenerator,在编译时读取swagger.json的内容并解析,成功生成了我们API的请求和响应类的代码。
我们可以发现,代码生成没有问题,无法移动或者编辑生成的代码。
下一篇文章我们就来学习下如何输出SourceGenerator生成的代码文件到我们的文件目录。
本文代码仓库地址https://github.com/fanslead/Learn-SourceGenerator