LightNode 1.2.0 - Swagger統合によるAPIのデバッグ実行

グラニのC#フレームワークの過去と未来、現代的なASP.NETライブラリの選び方という記事で、スライドと補足は先に上げましたが、以前に弊社で行った勉強会「Build Insider MEETUP with Grani 第1回」のレポートがBuild Insiderに上がっています。「using CSharp;」な企業を支える技術方針とベスト.NETライブラリ記事によるレポートは、さくさく読めるし、スライドで欠けていた部分も補完できていーんじゃないでしょーか。まる。

さて、で、基本的にAPIサーバーはOWINで行くんですが、API開発はとにかくふつーのウェブよりも開発がしにくい!の欠点を、前回LightNode 1.0、或いはWeb APIでのGlimpseの使い方はGlimpseフル統合で補おうとしました。それはそれでいいんですが、もう一つ足りない。それはともかく根本的にそもそも実行しづらい。さすがにモバイルのエミュレーターなりUnityのEditorなりから毎度実行は効率悪すぎてありえないし、POSTMANFiddlerで叩くのも面倒くせえ。

そこでSwagger。とはなにか、というのは見てもらえれば。

実行機付きのAPIのヘルプ・ドキュメントです。ヘルプ/ドキュメントはどうでもいいんですが、この実行機がかなり使いやすくいい!パラメータ入力してTry it out!でOK。認証が必要な場合も、右上にapi_keyというのが見えてますが、ちょっとindex.htmlを書き換えてこのapi_keyの部分を好きに都合のいい形態に変えてしまえば、機能します。実に便利。

SwaggerはAzureのAPI Appsでも利用されるようになったので、今後.NETでも目にする機会はちょっとずつ増えていくのではないでしょうか?ASP.NET Web APIで利用する方法はみそせんせーのSwagger を使った ASP.NET Web API のドキュメント生成を参照すれば良いでしょふ。

さて、LightNode(ってここではじめて解説しますが、私の作っているOwin上で動くMicro REST/RPCフレームワークです)では、Swagger統合はMiddlewareとして実装してあります。

ルートをapiと別系統にswaggerとして切ってもらって、

// 今のところPOSTしかサポートしてないのでPostを有効にしてね
app.Map("/api", builder =>
{
    builder.UseLightNode(new LightNodeOptions(AcceptVerbs.Get | AcceptVerbs.Post, new JilContentFormatter(), new GZipJilContentFormatter())
    {
        ParameterEnumAllowsFieldNameParse = true, // Enumを文字列で並べたいならこれをONにして
        // 下2つはSwagger前提で使うならエラー表示的に便利
        ErrorHandlingPolicy = ErrorHandlingPolicy.ReturnInternalServerErrorIncludeErrorDetails,
        OperationMissingHandlingPolicy = OperationMissingHandlingPolicy.ReturnErrorStatusCodeIncludeErrorDetails
    });
});

// こっちでSwaggerを有効にする
app.Map("/swagger", builder =>
{
    // XMLコメントから引っ張ってくるばあい(オプション)はパスを指定してください
    // メソッドに付与されているsummary, remarks, paramを情報として使います     
    var xmlName = "LightNode.Sample.GlimpseUse.xml";
    var xmlPath = System.AppDomain.CurrentDomain.BaseDirectory + "\\bin\\" + xmlName; // もしくは HttpContext.Current.Server.MapPath("~/bin/" + xmlName);

    // LightNode側のAPIのbasePathを指定
    builder.UseLightNodeSwagger(new Swagger.SwaggerOptions("LightNodeSample", "/api")
    {
        XmlDocumentPath = xmlPath,
        IsEmitEnumAsString = true // Enumを文字列で並べたいならtrueに
    });
});

といった感じです。ちょっとややこしーですが、基本的にはUseLightNodeSwaggerだけでOK、ということで。これで、例えば http://localhost:41932/Swagger/ にアクセスすればSwaggerの画面が出てきます。Swagger-UI自体はdllに埋め込まれています。また、定義ファイル(JSON)はapi-default.jsonにアクセスすることで、直接取得できます。

もしOwinをIISでホストしている場合、IISのStaticFileハンドラーが邪魔してうまくホストできない場合があります。その場合、StaticFileハンドラーを殺してください。Owinでやる場合は、とにかくOwinに寄せたほうがいいですね(StaticFile系はMicrosoft.Owin.StaticFiles使いましょう)

<system.webServer>
    <handlers>
        <remove name="StaticFile" />
        <!-- もしGlimpseもホストする場合はGlimpseのを先に書いといて -->
        <add name="Glimpse" path="glimpse.axd" verb="GET" type="Glimpse.AspNet.HttpHandler, Glimpse.AspNet" preCondition="integratedMode" />
        <add name="OWIN" path="*" verb="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler" />
    </handlers>
</system.webServer>

もし、例えば最初に例に出しましたが、認証情報を付与するとかでindex.htmlを埋め込みのではなくカスタムのを使いたい場合、OptionのResolveCustomResourceをハンドリングすればできます。例えばこんな感じに、別の埋め込みリソースから取り出したものに差し替えたり。

app.Map("/swagger", builder =>
{
    builder.UseLightNodeSwagger(new LightNode.Swagger.SwaggerOptions("MySample", "/api")
    {
        ResolveCustomResource = (filePath, loadedEmbeddedBytes) =>
        {
            if (filePath == "index.html")
            {
                using (var resourceStream = typeof(Startup).Assembly.GetManifestResourceStream("MySample.Swagger.index.html"))
                using (var ms = new MemoryStream())
                {
                    resourceStream.CopyTo(ms);
                    return ms.ToArray();
                }
            }
            return loadedEmbeddedBytes;
        }
    });
});

当然、index以外でもハンドリングできます。

まとめ

GlimpseとSwaggerがあわさって最強に見える!あとはもともとあるクライアント自動生成もあるので、三種の神器コンプリート。実際、これでAPI開発の苦痛に思えるところがかなり取り除かれたのではないかなー、って思ってます。これを全部自分で用意するのはそれはそれは大変なので、Owinで良かったし、組み合わせに関しても、.NETでOSSを使うってこういうことですよね?という例になればよいかな。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive