When you provide your service as a Web API, the question arises of how to inform the user about its capabilities, the syntax of its requests, etc. Usually you have to create separate Web pages where you open these topics. But would it not be better if your Web API itself provided access to its documentation?
If you open the page of some serious project on
GitHub , you will see a well-designed
Readme.md . This
Markdown document describes the purpose of the code stored in the repository, and often contains references to other documents. GitHub automatically converts Markdown to an HTML representation and shows you the result in an easy-to-read form. This makes Markdown files a convenient way to store documentation about your project. First of all, this format provides quite rich possibilities for text formatting. In addition, these files are stored in your version control system (VCS) along with your code. This makes such documents equal with the code files themselves (first-class citizens). You view them as part of the code and change them when you make modifications to the code. At least it should be in theory. Now you have all the documentation in your repository.
If your repository is open, then everything is fine. Users of your API can see the documentation there. But I work for a company that provides some Web API to external clients. These clients do not have access to our repositories. How do we provide them with documentation on our services?
You can create a separate website with documentation. But then we will have 2 places where product information is stored: in Markdown files and on this site. You can, of course, automate the process of creating a site with documentation, generating it from Markdown documents. Or you can create a separate document (for example, PDF), which includes the contents of all these files.
')
There is nothing wrong with this approach. But, I think that it is possible to make one more step in this direction. Why do we separate the documentation from the API itself? Is it possible to ship them together? For example, our Web API is available at
www.something.com/api/data , and its documentation is available at
www.something.com/api/help.mdHow hard is it to implement this approach in the ASP.NET Web API? Let's get a look.
Let's start with a simple web API based on OWIN. Here is my
Startup file:
[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; } } }
Let's add some markdown files to the project:

A few words should be said about the added files. First of all, we can have a complex subfolder structure that stores various parts of our documentation. In addition, we have other files, not just Markdown. For example, our documentation may contain images that Markdown documents will refer to. Therefore, our solution for providing documentation via the Web API must support both the folder structure and additional files.
We will start the changes with the
Web.config file. It needs to make some modifications. The fact is that Internet Information Services (IIS) can deliver static files to a user without the participation of our application. For example, if a user requests
myhost / help / root.md , IIS will understand that there is such a file on the disk and will return it himself. This means that IIS will not pass the request to our application. But this is not what we want. We do not need to return the raw Markdown file, we want to first convert it to HTML. That is why we need to make changes in
Web.config . You need to tell IIS that all requests should be sent to our application, without trying to execute them yourself. This can be done by setting the
system.webServer section:
<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>
Now IIS will not process static files. But we still have to supply them (for example, for pictures in our documentation). Therefore, we will use the NuGet package
Microsoft.Owin.StaticFiles . So, if we want our documentation to be available at
/ api / doc , we need to configure this package as follows:
[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; } } }
Now we are returning static files from the
Help folder of our application to the address
/ api / doc . But we still need to somehow convert the Markdown documents to HTML before returning them. For this purpose, we will write our OWIN middleware. This middleware will use the
Markdig NuGet package.
[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);
Let's see how this middleware works. First of all, it checks whether the Markdown file was requested, or something else. This is
what the GetMarkdownFile function
does . It tries to find a Markdown file matching the request and returns it to
FileInfo if the file is found, or null if it is not found. The implementation is not the best, but it serves to test the idea. It can be replaced with any other implementation.
If the file was not found, the middleware passes the request processing further using
await next () . But if the file is found, its contents are read, converted to HTML and returned in response.
Now we have documentation that the user can see in several places. It can be viewed in the VCS repository (for example, in GitHub). It is also directly accessible through our Web API. And besides, the documentation is part of our code, which we store under VCS.
I think this is a very good result. However, its disadvantages should be discussed.
First of all, such a system is good if your product is already stable. But in the early stages of development, it is not always clear what your API should look like, what format requests and responses should have, etc. At this stage the documentation should be open for comments. Therefore, you will need some tool that allows you to comment on the contents of Markdown-files. GitHub has an Issues system where you can leave your comments about the code. Since the documentation is now part of the code, Issues can be used to discuss its contents at the design stage. But personally, I think this is not the best solution. It would be much more convenient to write comments directly in the document, as it is possible to do in
Confluence . In short, I believe that a good tool is needed to discuss Markdown documents in the early stages of development.
My colleagues at
Confirmit have pointed out several other shortcomings of the solution described, which I should mention. Delivering documentation along with the API can adversely affect the speed of your service, since now one
ThreadPool will be used both to serve requests to the Web API itself and to request documentation.
In addition, adding an access point to the documentation extends the attack surface of your service. You need to decide whether you provide access to documentation only to authorized users, or anyone. In the latter case, the opportunity for DoS attacks on the access point to the documentation. And since the documentation is supplied by the same service as the Web API, this may adversely affect the work of the API itself.
This concludes the article. I hope the idea presented in it will be useful at least as a starting point for further research.