Web API Help Documentation Pointers

This is partially a reference post for myself and partially way to collect a few bits of knowledge I found on the web into one place. I've recently put together a web API project for a client and I wanted the documentation to be as nice as possible when handing it off; what follows is some easy stuff you can do to make the generated help pages more useful.

If you use the default project templates in VS 2013 for creating a web API project, one of the benefits is the very cool API help documentation pages. These are automatically generated for you, and are cordoned off in their own area of the MVC site. Auto-generated API documentation was one of the features that initially attracted me to FubuMVC (RIP), and it's great to see that option in Web API.

The out-of-the-box experience isn't bad, but there are a few things you'll probably want to do in order to make the help even more useful.

Filling in the Description fields

Out of the box you'll get a list of the endpoints for each controller and "No documentation available" for all of the Descriptions. So the first thing we want to do is add those descriptions. I've faked up a controller for doing CRUD operations on Addresses for demonstration purposes. Here's what the Address controller looks like before adding any help info:

And here's what the help page looks like:

Help Page with Defaults

We can fill in those description fields using triple-slash comments on our controller. I found out how to do this from a post by Mike Wasson; you just need to change your project settings (under the Build tab) to output an XML documentation file. Set the output file to App_Data\XmlDocument.xml, then find the HelpPageConfig.cs file (it will be under /Areas/HelpPage/App_Start) and add the following code to the Register method:

config.SetDocumentationProvider(
    new XmlDocumentationProvider(
        HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));

At application startup, this will take the generated XML documentation and use it to generate your help pages. Which means that triple-slash documentation will turn into useful Description fields.

Here's the Address Controller with comments:

And now the help page looks like this:

Help Page with Defaults

This applies to the models returned by your endpoints - the description fields on the individual endpoint help pages will also reflect your triple-slash comments.

Ugh, that's a lot of warnings

Turning on the XML documentation feature gives us nice API documentation, but it has the side effect of adding tons of warnings to our project, in the form of "Missing XML comment for publicly visible type or member". Which means we have to go through the project and document nearly everything if we want to treat warnings as errors (or even if we want to be able to see any other types of warning in the sea of missing XML comment warnings). If you want to use this feature but don't want to deal with the flood of comment warnings, you can suppress this particular warning in your project settings by entering 1591 in the 'Suppress Warnings' field.

Removing endpoints from the help page

For whatever reason, you might not want all the endpoints to show up in the help section. You can suppress an endpoint by modifying its ApiExplorerSettings. If I wanted to remove the PUT endpoint from the help, I can just do something like this:

[ApiExplorerSettings(IgnoreApi = true)]
public void Put(int id, AddressInputModel address)
{
    Addresses.Update(id, address);
}

Also, you may want to go the other direction, and only show a few endpoints. I haven't tested it yet (because I haven't needed it), but I found a post about making the help page opt-in instead of opt-out.

Showing a Response Type when your endpoints return IHttpActionResult

You may have noticed that the AddressController is directly returning an AddressViewModel object from the Get(int id) method:

public AddressViewModel Get(int id)
{
    return Addresses.Get(id);
}

When you return a type directly from your controller method, the documentation will happily generate a sample return object, which is nice. However, pretty much nobody actually returns a view model directly from a controller method - this is a REST API, so we want to return proper HTTP status codes. Most people use IHttpActionResult for this:

public IHttpActionResult Get(int id)
{
    var viewModel = Addresses.Get(id);

    if(viewModel == null)
    {
        return NotFound();
    }

    return Ok(viewModel);
}

This is (a lot) more correct from a REST perspective, but it breaks our documentation. The help engine no longer knows that the response is an AddressViewModel object. But we can tell it using the ResponseType attribute:

[ResponseType(typeof(AddressViewModel))]
public IHttpActionResult Get(int id){...}

The help engine will pick that up and correctly generate the sample response data on the help page.

Better sample data

Out of the box the help pages will do their best to display some sample input and response objects based on the data types in your input and output models:

Help Page with Defaults

But these samples are boring, not terribly helpful, and might even be a bit misleading. So you might want to set up some nicer sample data.

In HelpPageConfig.cs, you can set sample objects which the help engine will use whenever it needs to display a particular model type. in the Register method, you just need to call the SetSampleObjects method. It might look something like this:

config.SetSampleObjects(new Dictionary<Type, object>
{
    {typeof(string), "sample string"},
    {typeof(IEnumerable<string>), new string[]{"sample 1", "sample 2"}},
    {typeof(AddressInputModel), new AddressInputModel()
   {
       City = "Springfield",
       Line1 = "742 Evergreen Terrace",
       PostalCode = "88888",
       State = "OH"
   })}
});

(Depending on how you created your project, there might already be a call to SetSampleObjects commented out in your HelpPageConfig.cs.)

Now when you visit the help page for an endpoint which takes AddressInputModel as an input, you'll see this sample data:

Help Page with Defaults

The downside of simply adding each model type to the SetSampleObjects call is that you have to remember to do it for each model type you add, and you have to maintain the sample objects as your API changes and grows. It's easy to let something like that stagnate, not picking up new fields or showing sample data which no longer makes sense. Plus, with multiple collaborators that central file can become a point of merge contention.

To make things a bit less painful, I like to add a simple interface to the project:

internal interface IProvideSampleApiObject
{
    KeyValuePair<Type, object> CreateSampleObjectsEntry();
}

I add that interface to any model types which should provide sample data:

public class AddressInputModel : IProvideSampleApiObject
{
    ...

    public KeyValuePair<Type, object> CreateSampleObjectsEntry()
    {
        return new KeyValuePair<Type, object>(GetType(),
           new AddressInputModel()
           {
               City = "Springfield",
               Line1 = "742 Evergreen Terrace",
               PostalCode = "88888",
               State = "OH"
           });
    }
}

This way, I'm maintaining the sample data right there in the class; it's more apparent if there are missing fields or sample data which doesn't make sense. Plus, the sample data can provide a little bit more documentation of intent to whoever is working on the class.

With this interface in place, we just need a few lines in the Register method of HelpPageConfig.cs to pick up our sample objects:

var types = Assembly.GetAssembly(typeof(HelpPageConfig)).GetTypes()
    .Where(type => type.IsClass && !type.IsAbstract)
    .Where(type => typeof(IProvideSampleApiObject).IsAssignableFrom(type));

var sampleObjectDictionary = types.Select(type => ((IProvideSampleApiObject) Activator.CreateInstance(type))
    .CreateSampleObjectsEntry()).ToDictionary(entry => entry.Key, entry => entry.Value);

config.SetSampleObjects(sampleObjectDictionary);

This assembly scan only runs once, at application startup. It picks up all of our model objects which provide sample data and adds their samples to the dictionary.