學習Source Generators之從swagger中生成類

来源:https://www.cnblogs.com/fanshaoO/p/18109928
-Advertisement-
Play Games

前面學習了一些Source Generators的基礎只是,接下來就來實踐一下,用這個來生成我們所需要的代碼。 本文將通過讀取swagger.json的內容,解析並生成對應的請求響應類的代碼。 創建項目 首先還是先創建兩個項目,一個控制台程式,一個類庫。 添加swagger文件 在控制台程式中添加F ...


前面學習了一些Source Generators的基礎只是,接下來就來實踐一下,用這個來生成我們所需要的代碼。
本文將通過讀取swagger.json的內容,解析並生成對應的請求響應類的代碼。

創建項目

首先還是先創建兩個項目,一個控制台程式,一個類庫。
image.png

添加swagger文件

在控制台程式中添加Files目錄,並把swagger文件放進去。別忘了還需要添加AdditionalFiles。

<ItemGroup>
  <AdditionalFiles Include="Files\swagger.json" />
</ItemGroup>

image.png

實現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。
image.png
點開一個文件,可以看到內容,並且在上方提示自動生成,無法編輯。
image.png
到這我們就完成了通過swagger來生成我們的請求和響應類的功能。

結語

本文章應用SourceGenerator,在編譯時讀取swagger.json的內容並解析,成功生成了我們API的請求和響應類的代碼。
我們可以發現,代碼生成沒有問題,無法移動或者編輯生成的代碼。
下一篇文章我們就來學習下如何輸出SourceGenerator生成的代碼文件到我們的文件目錄。

本文代碼倉庫地址https://github.com/fanslead/Learn-SourceGenerator


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Avalonia中的Window 在Avalonia中,Window是一個基本的UI元素,它代表了一個應用程式的視窗。每個Window都可以包含其他的UI元素,如按鈕、文本框等,並可以響應各種用戶輸入事件。 在下麵的例子中,制定了當前應用的Window是MainWindow public parti ...
  • 目錄1.Redis簡介2.使用場景3.C# 具體使用介紹(Nuget)StackExchange.RedisFreeRedisNewLife.RedisServiceStack.Redis (收費)4.Redis 常用面試問題以及回答5.建議及經驗分享建議Redis 經驗分享ShareFlow 1. ...
  • 上一篇介紹了 IL 指令的分類以及參數載入指令,該載入指令以ld開頭,將參數載入到棧中,以便於後續執行操作命令。本篇開始介紹參數存儲指令,其指令以st開頭,將棧中的數據,存儲到指定的變數中,以方便後續使用。 ...
  • 引言 在現代化的軟體開發中,單元測試和集成測試是確保代碼質量和可靠性的關鍵部分。ASP.NET Core 社區內提供了強大的單元測試框架,xUnit 是其中之一,它提供了簡單、清晰和強大的測試功能,編寫單元測試有許多優點;有助於回歸、提供文檔及輔助良好的設計。下麵幾節我們來深入淺出探討如何使用 xU ...
  • 隨著跨平臺應用的需求不斷增長,開發人員需要一種能夠在不同操作系統上運行的用戶界面(UI)框架。 Avalonia 是一種引人註目的選擇。在本文中,我們將深入瞭解 Avalonia 是什麼,它與 WPF 的區別,以及它的 UI 繪製引擎和原理、優點,以及一個簡單的示例代碼。 Avalonia 是什麼? ...
  • iNeuOS工業互聯網操作系統“表單設計”功能經過升級後,能夠適用於更多應用場景,從業務上來講可以擴展設備管理、MES等表單類的管理功能,從技術上來講可以支持資料庫單表應用、多級關聯表應用、可以自定義寫SQL語句等,現在支持22個基礎表單組件、9個高級表單組件。 ...
  • 本篇教程深入探討了 ILGenerator 中的參數載入指令,通過詳細解釋Ldarg、Ldarga、Ldloc和Ldloca等指令的使用,讀者能夠清晰地認識到Ld指令用於載入參數或本地變數到堆棧,而St指令用於將值從堆棧存儲到參數或本地變數中。這些指令為動態方法的生成提供了基礎,幫助開發者更好地掌握... ...
  • CSharpe線程 目錄CSharpe線程C#如何操作線程Thread1. Thread如何開啟一個線程呢?2. Thread中常見的API3. thread的擴展封裝threadpool一、 .NET Framework2.0時代:出現了一個線程池ThreadPool二、線程池如何申請一個線程呢? ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...