[AzureFunctions][C#]NSwagでSwagger(2.0)に対応する

C# C#

NSwagを使ってAzure FunctionsでSwagger UIを表示できるようにしました!(Swaggerファイルの書き出し[2.0]も)
OpenApi3.0も対応しているしAPI Managementで管理してもいいのでは。と思ったけど、どうしても使いたいという要望もありやってみました。

参考

こちらのリポジトリを参考に実装しました。(公式?)
https://github.com/Jusas/NSwag.AzureFunctionsV2

下記プロジェクトをスタートアップに設定して動かすと、Swagger UIが表示されます。
NSwag.SwaggerGeneration.AzureFunctionsV2.Tests.HttpExtensionsTestApp

SwaggerのレスポンスやパラメータはAttributeを使います。下記に詳しく書いてます。
https://github.com/Jusas/NSwag.AzureFunctionsV2/wiki

SwaggerのAzure Functions対応手順

Nugetから必要なライブラリ導入

下記ライブラリを入れました。

<PackageReference Include="NSwag.SwaggerGeneration" Version="12.3.0" />
<PackageReference Include="NSwag.SwaggerGeneration.AzureFunctionsV2" Version="1.1.3" />
<PackageReference Include="NSwag.Core" Version="13.1.5" />
<PackageReference Include="NSwag.Annotations" Version="13.1.5" />
<PackageReference Include="NSwag.AspNetCore" Version="13.1.5" />

SwaggerUIディレクトリコピー

上記参考のリポジトリからSwaggerUIディレクトリをコピーします。
このディレクトリにはSwaggerを表示するための静的ファイルが入っているようです。
コピーしたら、下記のようにビルドオプションで埋め込みリソースにします。

Swaggerエンドポイント作成

SwaggerEndpoints.csをコピー

Swaggerのエンドポイントを作るためにcloneしたリポジトリからSwaggerEndpoints.csをコピーします。
ファイル構成は下記のようになりました。(SwaggerUIとSwaggerEndpoints.csを追加)

ファイルを環境に合わせて修正

下記のような感じになりました。手を入れないといけないのが
Swagger関数内のsettingsをgeneratorに渡しているところと、SwaggerUI関数内の静的ファイルへのパスの箇所です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using NSwag.Annotations;
using NSwag.SwaggerGeneration.AzureFunctionsV2;

namespace TestNSwag
{
    public static class SwaggerEndpoints
    {
        /// <summary>
        /// Generates Swagger JSON.
        /// </summary>
        /// <param name="req"></param>
        /// <param name="log"></param>
        /// <returns></returns>
        [OpenApiIgnore]
        [FunctionName("swagger")]
        public static async Task<IActionResult> Swagger(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]
            HttpRequest req,
            ILogger log)
        {
            // swaggerUIの設定 
            var settings = new AzureFunctionsV2ToSwaggerGeneratorSettings();
            settings.Title = "TestNSwag";
            var generator = new AzureFunctionsV2ToSwaggerGenerator(settings);

            // UIに表示したいクラスを記載する
            var funcClasses = new[]
            {   
                typeof(TestNSwag),
            };
            var document = await generator.GenerateForAzureFunctionClassesAsync(funcClasses, null);

            // Workaround for NSwag global security bug, see https://github.com/RicoSuter/NSwag/pull/2305
            document.Security.Clear();

            var json = document.ToJson();
            return new OkObjectResult(json);
        }

        /// <summary>
        /// Serves SwaggerUI files.
        /// </summary>
        /// <param name="req"></param>
        /// <param name="staticfile"></param>
        /// <param name="log"></param>
        /// <returns></returns>
        [OpenApiIgnore]
        [FunctionName("swaggerui")]
        public static async Task<IActionResult> SwaggerUi(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "swaggerui/{staticfile}")] HttpRequest req,
            string staticfile,
            ILogger log)
        {
            var asm = Assembly.GetAssembly(typeof(SwaggerEndpoints));

            // アクセスされた時のパスをゴニョゴニョしているので環境に合わせる
            var files = asm.GetManifestResourceNames().Select(x => x.Replace("TestNSwag.SwaggerUi.", ""))
                .ToList();

            Console.WriteLine(files);

            int index = -1;
            if ((index = files.IndexOf(staticfile)) != -1)
            {
                var fileExt = staticfile.Split('.').Last();
                var types = new Dictionary<string, string>()
                {
                    {"png", "image/png"},
                    {"html", "text/html"},
                    {"js", "application/javascript"},
                    {"css", "text/css"},
                    {"map", "application/json"}
                };
                var fileMime = types.ContainsKey(fileExt) ? types[fileExt] : "application/octet-stream";
                using (var stream = asm.GetManifestResourceStream(asm.GetManifestResourceNames()[index]))
                {
                    var buf = new byte[stream.Length];
                    await stream.ReadAsync(buf, 0, buf.Length);
                    return new FileContentResult(buf, fileMime);
                }
            }

            return new NotFoundResult();

        }
    }
}

Swaggerを書く(サンプル)

Azure Functionに下記のようにAttributeを付けて表現できます。
下記は初期でテンプレートとして作成されるものにSwaggerを記述しています。
SwaggerQueryParameterでクエリパラメータ、SwaggerResponseでレスポンスを書いています。

public static class TestNSwag
{
    /// <summary>
    /// SwaggerTest
    /// </summary>
    /// <param name="req"></param>
    /// <param name="log"></param>
    /// <returns></returns>
    [SwaggerQueryParameter("name", Required = false, Type = typeof(string), Description = "ユーザー名")]
    [SwaggerResponse(HttpStatusCode.OK, typeof(string), Description = "表示結果")]
    [SwaggerResponse(HttpStatusCode.InternalServerError, typeof(string), Description = "内部エラー")]
    [FunctionName("TestNSwag")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        string name = req.Query["name"];

        string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        name = name ?? data?.name;

        return name != null
            ? (ActionResult)new OkObjectResult($"Hello, {name}")
            : new BadRequestObjectResult("Please pass a name on the query string or in the request body");
    }
}

動作確認

エンドポイントはそのままだと下記になります。
Swagger => http://localhost:7071/api/swagger
UI => http://localhost:7071/api/swaggerui/index

実行するとSwaggerUIとSwaggerのURLが表示されて、アクセスすると下記のように使えることが確認できました。
情報が少なかったので結構大変でした😵

githubにソース置いてます。
https://github.com/YasuakiHirano/TestNSwag

コメント

タイトルとURLをコピーしました