はじめに
Swashbuckle はまだプレリリースの段階ではあるけど、 ASP.NET Core にも対応しているみたい。
Swashbuckle を使えば、Web API の実装から Swagger Definitions を生成できる。
温かみのある手作業で、 Web API の Swagger Definitions を書いていたけど、 もう限界なので試してみることにした。
プロジェクト新規作成
今回は Web API だけ使うので、 ASP.NET Core の Web API プロジェクトを作成しておく。
Swashbuckle をインストール
project.json
の dependencies
に
"Swashbuckle": "6.0.0-beta902"
を追加して、パッケージをリストア。
Swagger を使うように Startup を修正
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace SwaggerSample { public class Startup { public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); } public IConfigurationRoot Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(); services.AddSwaggerGen(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseMvc(); // Swagger Definitions を出力できるようにする app.UseSwagger(); // Swagger UI を表示できるようにする app.UseSwaggerUi(); } } }
モデルを定義
Web API で返すモデルだけでなく、 入力用のモデルと、 エラー用のモデルも定義してみた。
using System.Collections.Generic; using System.ComponentModel.DataAnnotations; namespace SwaggerSample.Models { public class Item { public string Id { get; set; } public string Title { get; set; } public string Description { get; set; } } public class ItemInputModel { [Required] public string Title { get; set; } [Required] public string Description { get; set; } } public class ErrorModel { public Dictionary<string, string> Errors { get; } = new Dictionary<string, string>(); } }
コントローラーを作成
プロジェクトを新規作成すると ValuesController ってのが作られるけど、 そいつは削除してしまって、 ItemsController を作成。
using System; using System.Collections.Generic; using System.Collections.Concurrent; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using SwaggerSample.Models; namespace SwaggerSample.Controllers { [Route("api/[controller]")] public class ItemsController : Controller { private static readonly ConcurrentDictionary<string, Item> _items = new ConcurrentDictionary<string, Item>(); [HttpGet] [ProducesResponseType(typeof(IEnumerable<Item>), 200)] public IActionResult Get() { return Ok(_items.Values); } [HttpGet("{id}")] [ProducesResponseType(typeof(Item), 200)] [ProducesResponseType(typeof(ErrorModel), 404)] public IActionResult Get(string id) { Item item; if (_items.TryGetValue(id, out item) == false) { return NotFound(new ErrorModel { Errors = { [ "id"]= $"ID = {id} のアイテムは存在しません。" } }); } else { return Ok(item); } } [HttpPost] [ProducesResponseType(typeof(Item), 200)] [ProducesResponseType(typeof(ErrorModel), 400)] public IActionResult Post([FromBody]ItemInputModel inputModel) { if (ModelState.IsValid == false) { return BadRequest(CreateValidationError()); } var item = new Item { Id = Guid.NewGuid().ToString(), Title = inputModel.Title, Description = inputModel.Description, }; _items.TryAdd(item.Id, item); return Ok(item); } [HttpPut("{id}")] [ProducesResponseType(typeof(Item), 200)] [ProducesResponseType(typeof(ErrorModel), 400)] [ProducesResponseType(typeof(ErrorModel), 404)] public IActionResult Put(string id, [FromBody]ItemInputModel inputModel) { Item item; if (_items.TryGetValue(id, out item) == false) { return NotFound(new ErrorModel { Errors = { ["id"] = $"ID = {id} のアイテムは存在しません。" } }); } if (ModelState.IsValid == false) { return BadRequest(CreateValidationError()); } item.Title = inputModel.Title; item.Description = inputModel.Description; _items[id] = item; return Ok(item); } [HttpDelete("{id}")] [ProducesResponseType(typeof(Item), 200)] [ProducesResponseType(typeof(ErrorModel), 404)] public IActionResult Delete(string id) { Item item; if (_items.TryRemove(id, out item)) { return Ok(item); } else { return NotFound(new ErrorModel { Errors = { ["id"] = $"ID = {id} のアイテムは存在しません。" } }); } } private ErrorModel CreateValidationError() { var error = new ErrorModel(); foreach (var pair in ModelState) { error.Errors.Add( pair.Key, string.Join( Environment.NewLine, pair.Value.Errors.Select(e => e.ErrorMessage) ) ); } return error; } } }
戻り値の型を IActionResult にする場合、 ProducesResponseTypeAttribute を使って、 Swashbuckle にモデルの型を教えてあげないといけない。 NotFound とか BadRequest とか扱いたいなら必須。
Swagger UI を表示
Visual Studio から実行したいところだけど、マシンパワーが貧弱で起動が遅いので、
> dotnet run
でサービスを実行。
ブラウザで localhost:5000/swagger/ui
にアクセスしてみる。
Swagger UI が表示された。 ここから Web API を実際に呼び出すことができて便利。
Swagger Definitions を表示
Swagger UI に Swagger Definitions の URL が表示されているので、 ブラウザでアクセスしてみる。
Swagger Definitions の JSON が表示された。 モデルの情報もちゃんと含まれている。
おわりに
Swagger UI は Web API のデバッグに超便利だった。 Swagger Definitions も生成してくれるので、 Swagger Codegen に渡せばクライアントのソースコードまで生成できる。
Swagger Codegen のインストールが面倒なら、 公式がホストしている Swagger Editor に貼り付けて生成する手もある。
Swagger Definitions を YAML でゴリゴリ書いていたけど、 Web API からの自動生成を経験してしまったので、 もう戻れないな。