ASP.NET Web APIのドキュメントサポート

Web APIの形式でサービスを提供する場合、ユーザーの機能、リクエストの構文などをユーザーに通知する方法が問題になります。 通常、これらのトピックをカバーする個別のWebページを作成する必要があります。 しかし、Web API自体がそのドキュメントへのアクセスを提供する方が良いと思いませんか?



GitHubで深刻なプロジェクトのページを開くと、適切に設計されたReadme.mdが表示されます 。 このマークダウンドキュメントでは、リポジトリに格納されているコードの目的について説明し、多くの場合、他のドキュメントへのリンクが含まれています。 GitHubは自動的にMarkdownをHTML表現に変換し、結果を読みやすい形式で表示します。 これにより、Markdownファイルはプロジェクトに関するドキュメントを保存する便利な方法になります。 まず、このフォーマットはテキストをフォーマットするための非常に豊富な可能性を提供します。 さらに、これらのファイルはコードとともにバージョン管理システム(VCS)に保存されます。 これにより、そのようなドキュメントはコードファイル自体と同等の立場になります(一流の市民)。 それらをコードの一部と見なし、コードを変更するときに変更します。 少なくとも理論的にはそうあるべきです。 これで、リポジトリにすべてのドキュメントができました。



リポジトリが開いていれば、すべて問題ありません。 APIのユーザーはそこでドキュメントを見ることができます。 しかし、私はいくつかのWeb APIを外部クライアントに提供する会社で働いています。 これらのクライアントは、リポジトリにアクセスできません。 サービスに関するドキュメントをどのように提供できますか?



ドキュメントを含む別のサイトを作成できます。 ただし、製品情報が保存される場所は2つあります。Markdownファイルとこのサイトです。 もちろん、Markdownドキュメントから生成することで、ドキュメントを含むサイトを作成するプロセスを自動化できます。 または、これらすべてのファイルの内容を含む別のドキュメント(PDFなど)を作成できます。



このアプローチには何の問題もありません。 しかし、私はこの方向にもう一歩踏み出すことができると思います。 API自体からドキュメントを分離するのはなぜですか? それらを一緒に供給することは可能ですか? たとえば、Web APIはwww.something.com/api/dataで入手でき、そのドキュメントはwww.something.com/api/help.mdで入手できます



ASP.NET Web APIでこのアプローチを実装するのはどれくらい難しいですか? 見てみましょう。



簡単なOWINベースのWeb APIから始めましょう。 これが私のスタートアップファイルです。



[assembly: OwinStartup(typeof(OwinMarkdown.Startup))] namespace OwinMarkdown { public class Startup { public void Configuration(IAppBuilder app) { HttpConfiguration config = new HttpConfiguration(); config.Formatters.Clear(); config.Formatters.Add( new JsonMediaTypeFormatter { SerializerSettings = GetJsonSerializerSettings() }); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new {id = RouteParameter.Optional} ); app.UseWebApi(config); } private static JsonSerializerSettings GetJsonSerializerSettings() { var settings = new JsonSerializerSettings(); settings.Converters.Add(new StringEnumConverter { CamelCaseText = false }); settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); return settings; } } }
      
      





プロジェクトにいくつかのMarkdownファイルを追加しましょう:







追加されたファイルについていくつかの言葉を言う必要があります。 まず、ドキュメントのさまざまな部分を格納する複雑なサブフォルダー構造を作成できます。 さらに、Markdownだけでなく、他のファイルもあります。 たとえば、ドキュメントにはMarkdownドキュメントがリンクする画像が含まれている場合があります。 したがって、Web APIを介してドキュメントを提供するソリューションは、フォルダー構造と追加ファイルの両方をサポートする必要があります。



Web.configファイルを使用して変更を開始します。 いくつかの修正が必要です。 実際のところ、インターネットインフォメーションサービス(IIS)は、アプリケーションに参加せずに静的ファイルをユーザー自体に配信できます。 たとえば、ユーザーがmyhost / help / root.mdを要求すると 、IISはディスク上にそのようなファイルがあることを理解し、それ自体を返します。 これは、IISが要求をアプリケーションに転送しないことを意味します。 しかし、それは私たちが望むものではありません。 生のMarkdownファイルを返す必要はありません。最初にHTMLに変換します。 このために、 Web.configに変更を加える必要があります。 すべての要求は、自分で実行しようとせずにアプリケーションに渡す必要があることをIISに伝える必要があります。 これは、 system.webServerセクションを設定することで実行できます。



 <system.webServer> <modules runAllManagedModulesForAllRequests="true" /> <handlers> <remove name="ExtensionlessUrlHandler-Integrated-4.0" /> <remove name="OPTIONSVerbHandler" /> <remove name="TRACEVerbHandler" /> <add name="Owin" verb="" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, Microsoft.Owin.Host.SystemWeb" /> </handlers> </system.webServer>
      
      





IISは静的ファイルを処理しなくなります。 ただし、それらを提供する必要があります(たとえば、ドキュメントの写真用)。 したがって、 Microsoft.Owin.StaticFiles NuGetパッケージを使用します。 したがって、ドキュメントを/ api / docで利用できるようにするには、このパッケージを次のように構成する必要があります。



 [assembly: OwinStartup(typeof(OwinMarkdown.Startup))] namespace OwinMarkdown { public class Startup { private static readonly string HelpUrlPart = "/api/doc"; public void Configuration(IAppBuilder app) { var basePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; app.UseStaticFiles(new StaticFileOptions { RequestPath = new PathString(HelpUrlPart), FileSystem = new PhysicalFileSystem(Path.Combine(basePath, "Help")) }); HttpConfiguration config = new HttpConfiguration(); config.Formatters.Clear(); config.Formatters.Add( new JsonMediaTypeFormatter { SerializerSettings = GetJsonSerializerSettings() }); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new {id = RouteParameter.Optional} ); app.UseWebApi(config); } private static JsonSerializerSettings GetJsonSerializerSettings() { var settings = new JsonSerializerSettings(); settings.Converters.Add(new StringEnumConverter { CamelCaseText = false }); settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); return settings; } } }
      
      





ここで、アプリケーションのHelpフォルダーからアドレス/ api / docに静的ファイルを返します。 ただし、Markdownドキュメントを返す前に、何らかの方法でHTMLに変換する必要があります。 この目的のために、OWINミドルウェアを作成します。 このミドルウェアは、 Markdig NuGetパッケージを使用します。



 [assembly: OwinStartup(typeof(OwinMarkdown.Startup))] namespace OwinMarkdown { public class Startup { private static readonly string HelpUrlPart = "/api/doc"; public void Configuration(IAppBuilder app) { var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); app.Use(async (context, next) => { var markDownFile = GetMarkdownFile(context.Request.Path.ToString()); if (markDownFile == null) { await next(); return; } using (var reader = markDownFile.OpenText()) { context.Response.ContentType = @"text/html"; var fileContent = reader.ReadToEnd(); fileContent = Markdown.ToHtml(fileContent, pipeline); // Send our modified content to the response body. await context.Response.WriteAsync(fileContent); } }); var basePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; app.UseStaticFiles(new StaticFileOptions { RequestPath = new PathString(HelpUrlPart), FileSystem = new PhysicalFileSystem(Path.Combine(basePath, "Help")) }); HttpConfiguration config = new HttpConfiguration(); config.Formatters.Clear(); config.Formatters.Add( new JsonMediaTypeFormatter { SerializerSettings = GetJsonSerializerSettings() }); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new {id = RouteParameter.Optional} ); app.UseWebApi(config); } private static JsonSerializerSettings GetJsonSerializerSettings() { var settings = new JsonSerializerSettings(); settings.Converters.Add(new StringEnumConverter { CamelCaseText = false }); settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); return settings; } private static FileInfo GetMarkdownFile(string path) { if (Path.GetExtension(path) != ".md") return null; var basePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; var helpPath = Path.Combine(basePath, "Help"); var helpPosition = path.IndexOf(HelpUrlPart + "/", StringComparison.OrdinalIgnoreCase); if (helpPosition < 0) return null; var markDownPathPart = path.Substring(helpPosition + HelpUrlPart.Length + 1); var markDownFilePath = Path.Combine(helpPath, markDownPathPart); if (!File.Exists(markDownFilePath)) return null; return new FileInfo(markDownFilePath); } } }
      
      





このミドルウェアの仕組みを見てみましょう。 まず、Markdownファイルが要求されているかどうかを確認します。 GetMarkdownFile関数はこれを行います。 彼女は、要求に一致するMarkdownファイルを見つけようとし、ファイルが見つかった場合はFileInfoに返し、見つからない場合はnullを返します。 実装は最適ではありませんが、アイデアをテストするのに役立ちます。 他の実装に置き換えることができます。



ファイルが見つからなかった場合、ミドルウェアはawait next()を使用しリクエスト処理をさらに渡します。 ただし、ファイルが見つかった場合は、その内容が読み取られ、HTMLに変換されて応答で返されます。



これで、ユーザーがいくつかの場所で見ることができるドキュメントができました。 VCSリポジトリ(GitHubなど)で表示できます。 また、Web APIから直接利用できます。 さらに、ドキュメントは、VCSの下に保存するコードの一部です。



これは非常に良い結果だと思います。 ただし、その欠点について説明する必要があります。



まず、このようなシステムは、製品がすでに安定している場合に適しています。 しかし、開発の初期段階では、APIがどのように見えるか、どの形式のリクエストとアンサーが持つべきかなどは必ずしも明確ではありません この段階では、ドキュメントをコメント用に開いておく必要があります。 したがって、Markdownファイルの内容についてコメントするツールが必要です。 GitHubには、コードに関するコメントを残すことができる問題システムがあります。 ドキュメントはコードの一部になったため、Issuesを使用して、設計時にその内容を議論できます。 しかし、個人的には、これは最善の解決策ではないと思います。 Confluenceで行うことが可能であるため、ドキュメントにコメントを直接記述する方がはるかに便利です。 要するに、開発の初期段階でMarkdownドキュメントを議論するには良いツールが必要だと思います。



Confirmitの同僚は、説明したソリューションのさらにいくつかの欠点を指摘しました。 APIと一緒にドキュメントを配信すると、Web API自体へのリクエストの処理とドキュメントのリクエストの両方に1つのThreadPoolが使用されるため、サービスの速度に悪影響を与える可能性があります。



さらに、ドキュメントアクセスポイントを追加すると、サービスの攻撃対象領域が拡大します。 ドキュメントへのアクセスを許可されたユーザーのみに提供するか、他の人に提供するかを決定する必要があります。 後者の場合、ドキュメントアクセスポイントに対するDoS攻撃の可能性が生じます。 また、ドキュメントはWeb APIと同じサービスによって提供されるため、API自体の動作に悪影響を与える可能性があります。



これで記事は終わりです。 そこに提示されているアイデアが、少なくともさらなる研究の出発点として役立つことを願っています。



All Articles