学习Source Generators之从swagger中生成类

  • 学习Source Generators之从swagger中生成类已关闭评论
  • 63 次浏览
  • A+
所属分类:.NET技术
摘要

前面学习了一些Source Generators的基础只是,接下来就来实践一下,用这个来生成我们所需要的代码。
本文将通过读取swagger.json的内容,解析并生成对应的请求响应类的代码。

前面学习了一些Source Generators的基础只是,接下来就来实践一下,用这个来生成我们所需要的代码。
本文将通过读取swagger.json的内容,解析并生成对应的请求响应类的代码。

创建项目

首先还是先创建两个项目,一个控制台程序,一个类库。
学习Source Generators之从swagger中生成类

添加swagger文件

在控制台程序中添加Files目录,并把swagger文件放进去。别忘了还需要添加AdditionalFiles。

<ItemGroup>   <AdditionalFiles Include="Filesswagger.json" /> </ItemGroup> 

学习Source Generators之从swagger中生成类

实现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。
学习Source Generators之从swagger中生成类
点开一个文件,可以看到内容,并且在上方提示自动生成,无法编辑。
学习Source Generators之从swagger中生成类
到这我们就完成了通过swagger来生成我们的请求和响应类的功能。

结语

本文章应用SourceGenerator,在编译时读取swagger.json的内容并解析,成功生成了我们API的请求和响应类的代码。
我们可以发现,代码生成没有问题,无法移动或者编辑生成的代码。
下一篇文章我们就来学习下如何输出SourceGenerator生成的代码文件到我们的文件目录。

本文代码仓库地址https://github.com/fanslead/Learn-SourceGenerator