Microsoft.AspNetCore.Mvc.ApiExplorer を使って Web API ドキュメントを自作

開発している Web APIASP.NET Core 2.0 に移行しようと思っていたが、Web API のドキュメント生成に使っている Swashbuckle.AspNetCore がまだ 2.0 に対していないっぽくて一時中断。

ASP.NET Core MVC にはもとから Microsoft.AspNetCore.Mvc.ApiExplorer というのが提供されていて、これを使うことでちょっとした Web API のドキュメントを出力できる。ただ、SwaggerUI ほど高機能では無いし、Web API ドキュメントを表示するためのコントローラーとビューを自分で書く必要があるみたいだ。手軽ではない。

試してみたコードがこちら。

using System.Collections.Generic;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace SampleApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .Build();
    }

    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            // AddMvc 内で ApiExplorer も登録されている
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvcWithDefaultRoute();
        }
    }

    [Route("api/[controller]")]
    [ApiExplorerSettings(GroupName = "Values API", IgnoreApi = false)]
    public class ValuesController : Controller
    {
        [HttpGet]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        [HttpGet("{id}")]
        public string Get(int id)
        {
            return "value";
        }

        [HttpPost]
        public void Post([FromBody]string value)
        {
        }

        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value)
        {
        }

        [HttpDelete("{id}")]
        public void Delete(int id)
        {
        }
    }

    public class HomeController : Controller
    {
        readonly IApiDescriptionGroupCollectionProvider _apiExplorer;

        public HomeController(IApiDescriptionGroupCollectionProvider apiExplorer)
        {
            _apiExplorer = apiExplorer;
        }

        // API ドキュメントを表示する
        public IActionResult Index()
        {
            return View(_apiExplorer);
        }
    }
}

Web API のドキュメントを表示する Home/Index ビュー。

@using Microsoft.AspNetCore.Mvc.ApiExplorer
@model IApiDescriptionGroupCollectionProvider

<html>
<head>
    <meta charset="utf-8" />
    <title>Sample API</title>
</head>
<body>
    <h1>Sample API ドキュメント</h1>

    @foreach (var group in Model.ApiDescriptionGroups.Items)
    {
        <h2>@group.GroupName</h2>

        @foreach (var api in group.Items)
        {
            <h3>@api.HttpMethod @api.RelativePath</h3>

            <div class="parameters">
                <h4>パラメーター</h4>
                @if (0 < api.ParameterDescriptions.Count)
                    {
                    <table>
                        <thead>
                            <tr>
                                <th>名前</th>
                                <th></th>
                                <th>Constrains</th>
                                <th>デフォルト値</th>
                                <th>必須</th>
                            </tr>
                        </thead>
                        <tbody>
                            @foreach (var parameter in api.ParameterDescriptions)
                            {
                                <tr>
                                    <td>@parameter.Name,  (@parameter.Source.Id)</td>
                                    <td>@parameter.Type?.FullName</td>

                                    @if (parameter.RouteInfo != null)
                                    {
                                        <td>@string.Join(",", parameter.RouteInfo.Constraints?.Select(c => c.GetType().Name).ToArray())</td>
                                        <td>@parameter.RouteInfo.DefaultValue</td>
                                        <td>
                                            @if (parameter.RouteInfo.IsOptional == true)
                                            {
                                                <text></text>
                                            }
                                        </td>
                                    }
                                    else
                                    {
                                        <td></td>
                                        <td></td>
                                        <td></td>
                                    }
                                </tr>
                            }
                        </tbody>
                    </table>
                }
                else
                {
                    <i>なし</i>
                }
            </div>

            <div class="responses">
                <h4>レスポンス</h4>

                <table>
                    <thead>
                        <tr>
                            <th>ステータスコード</th>
                            <th></th>
                            <th>メディアタイプ</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var response in api.SupportedResponseTypes)
                        {
                            <tr>
                                <td>@response.StatusCode</td>
                                <td>@response.Type?.FullName</td>
                                <td>
                                    <ul>
                                        @foreach (var responseFormat in response.ApiResponseFormats)
                                        {
                                            <li>@responseFormat.MediaType</li>
                                        }
                                    </ul>
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        }
    }
</body>
</html>

実行すると、こんなドキュメントが表示される。

f:id:griefworker:20170817141951p:plain

取得した Web API の情報を自分で好きなように出力するので、自由度は高い。でもやっぱり Swagger UI の方がいいな。

2017 年 8 月 23 日訂正

ASP.NET Core 2.0 で Swashbuckle.AspNetCore 使えました。