One of the standard services added when you call AddMvc() or AddMvcCore() in an ASP.NET Core MVC application is the ApiExplorer. In this post I'll show a quick example of its capabilities, and give you a taste of the metadata you can obtain about your application.

Exposing your application's API with the ApiExplorer

The ApiExplorer contains functionality for discovering and exposing metadata about your MVC application. You can use it to provide details such as a list of controllers and actions, their URLs and allowed HTTP methods, parameters and response types.

How you choose to use these details is up to you - you could use it to auto-generate documentation, help pages, or clients for your application. The Swagger and Swashbuckle.AspNetCore frameworks use the ApiExplorer functionality to provide a fully featured documentation framework, and are well worth a look if that's what you're after.

For this article, I'll hook directly into the ApiExplorer to generate a simple help page for a basic Web API controller.

Basic Web API project

Adding the ApiExplorer to your applications

The ApiExplorer functionality is part of the Microsoft.AspNetCore.Mvc.ApiExplorer package. This package is referenced by default when you include the Microsoft.AspNetCore.Mvc package in your application, so you generally won't need to add the package explicitly. If you are starting from a stripped down application, you can add the package directly to make the services available.

As Steve Gordon describes in his series on the MVC infrastructure, the call to AddMvc in Startup.ConfigureServices automatically adds the ApiExplorer services to your application by calling services.AddApiExplorer(), so you don't need to explicitly add anything else to your Startup class .

The call to AddApiExplorer in turn, calls an internal method AddApiExplorerServices(), which adds the actual services you will use in your application:

internal static void AddApiExplorerServices(IServiceCollection services)  
{
    services.TryAddSingleton<IApiDescriptionGroupCollectionProvider, ApiDescriptionGroupCollectionProvider>();
    services.TryAddEnumerable(
        ServiceDescriptor.Transient<IApiDescriptionProvider, DefaultApiDescriptionProvider>());
}

This adds a default implementations of the IApiDescriptionGroupCollectionProvider that exposes the API endpoints to your application. To access the list of APIs, you just need to inject the service into your controller/services.

Listing your application's metadata

For this app, we'll just include the default ValuesController that is added to the default web API project:

[Route("api/[controller]")]
public class ValuesController : Controller  
{
    // GET api/values
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
}

In addition, we'll create a simple controller that renders details about the Web API endpoints in your application to a razor page, as you saw earlier, called DocumentationController.

First, inject the IApiDescriptionGroupCollectionProvider into the controller. For simplicity, we'll just return this directly as the model to the Razor view page - we'll decompose the details it provides in the Razor page.

public class DocumentationController : Controller  
{
    private readonly IApiDescriptionGroupCollectionProvider _apiExplorer;
    public DocumentationController(IApiDescriptionGroupCollectionProvider apiExplorer)
    {
        _apiExplorer = apiExplorer;
    }

    public IActionResult Index()
    {
        return View(_apiExplorer);
    }
}

The provider exposes a collection of ApiDescriptionGroups, each of which contains a collection of ApiDescriptions. You can think of an ApiDescriptionGroup as a controller, and an ApiDescription as an action method.

The ApiDescription contains a wealth of information about the action method - parameters, the URL, the type of media that can be returned - basically everything you might want to know about an API!

The Razor page below lists out all the APIs that are exposed in the application. There's a slightly overwhelming amount of detail here, but it lists everything you might need to know!

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

<div id="body">  
    <section class="featured">
        <div class="content-wrapper">
            <hgroup class="title">
                <h1>ASP.NET Web API Help Page</h1>
            </hgroup>
        </div>
    </section>
    <section class="content-wrapper main-content clear-fix">
        <h3>API Groups, version @Model.ApiDescriptionGroups.Version</h3>
        @foreach (var group in Model.ApiDescriptionGroups.Items)
            {
            <h4>@group.GroupName</h4>
            <ul>
                @foreach (var api in group.Items)
                {
                    <li>
                        <h5>@api.HttpMethod @api.RelativePath</h5>
                        <blockquote>
                            @if (api.ParameterDescriptions.Count > 0)
                            {
                                <h6>Parameters</h6>
                                    <dl class="dl-horizontal">
                                        @foreach (var parameter in api.ParameterDescriptions)
                                        {
                                            <dt>Name</dt>
                                            <dd>@parameter.Name,  (@parameter.Source.Id)</dd>
                                            <dt>Type</dt>
                                            <dd>@parameter.Type?.FullName</dd>
                                            @if (parameter.RouteInfo != null)
                                            {
                                                <dt>Constraints</dt>
                                                <dd>@string.Join(",", parameter.RouteInfo.Constraints?.Select(c => c.GetType().Name).ToArray())</dd>
                                                <dt>DefaultValue</dt>
                                                <dd>parameter.RouteInfo.DefaultValue</dd>
                                                <dt>Is Optional</dt>
                                                <dd>@parameter.RouteInfo.IsOptional</dd>
                                            }
                                        }
                                    </dl>
                            }
                            else
                            {
                                <i>No parameters</i>
                            }
                        </blockquote>
                        <blockquote>
                            <h6>Supported Response Types</h6>
                            <dl class="dl-horizontal">
                                @foreach (var response in api.SupportedResponseTypes)
                                {
                                    <dt>Status Code</dt>
                                        <dd>@response.StatusCode</dd>

                                        <dt>Response Type</dt>
                                        <dd>@response.Type?.FullName</dd>

                                        @foreach (var responseFormat in response.ApiResponseFormats)
                                        {
                                            <dt>Formatter</dt>
                                            <dd>@responseFormat.Formatter?.GetType().FullName</dd>
                                            <dt>Media Type</dt>
                                            <dd>@responseFormat.MediaType</dd>
                                        }
                                }
                            </dl>

                        </blockquote>
                    </li>
                }
            </ul>
        }
    </section>
</div>  

If you run the application now, you might be slightly surprised by the response:

Blank application

Even though we have the default ValuesController in the project, apparently, there's no APIs!

Enabling documentation of your controllers

By default, controllers in ASP.NET Core are not included in the ApiExplorer. There are a whole host of attributes you can apply to customise the metadata produced by the ApiExplorer, but the critical one here is [ApiExplorerSettings].

By applying this attribute to a controller, you can control whether or not it is included in the API, as well as its name in the ApiExplorer:

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

    // other action methods
}

After applying this attribute, and viewing the groups in IApiDescriptionGroupCollectionProvider you can see that the API is now available:

Basic Web API project

ApiExplorer and conventional routing

Note, you can only apply the [ApiExplorerSettings] attribute to controllers and actions that use attribute routing. If you enable the ApiExplorer on an action that uses conventional routing, you will be greeted with an error like the following:

Error when using ApiExplorer on conventional routing

Remember, ApiExplorer really is just for your APIs! If you stick to the convention of using attribute routing for your Web API controllers and conventional routing for your MVC controllers you'll be fine, but it's just something to be aware of.

Summary

This was just a brief introduction to the ApiExplorer functionality that exposes a variety of metadata about the Web APIs in your application. You're unlikely to use it quite like this, but it's interesting to see all the introspection options available to you.