WebAPI: автогенерация веб-документации REST API

в 16:56, , рубрики: .net, ASP.NET, asp.net webapi, rest, Блог компании Microsoft, Веб-разработка, документирование

В этой записи блога мы близко рассмотрим ApiExplorer, являющийся реализацией IApiExplorer по умолчанию и увидим как с помощью него можно быстро сгенерировать веб-документацию по доступному REST API. В этой документации будет содержаться разнообразная информация, например, правильные URL, допустимые HTTP-методы, ожидаемые для запросов параметры. Такого рода информация для вашего REST-сервиса позволит сторонним разработчикам, потребляющим ваш API, точно знать как правильно вызывать его части. Наверное, самое приятное в такой странице веб-документации состоит в том, что она будет обновляться автоматически вместе с обновлением вашего REST API.

ApiExplorer

Основной целью этого класса является генерирование коллекции элементов ApiDescription. Это производится с помощью статической проверки маршрутов и доступных действий внутри ваших контроллеров. Каждый элемент ApiDescription описывает API доступный через ваш сервис. Как вы можете видеть на упрощенной диаграмме (рисунок 1) ApiDescription содержит базовую информацию такую как, HttpMethod, RelativePath, Documentation и т.д. Но кроме того, он содержит элемент ApiDescriptor, который является частью ядра WebAPI знающей все о соответствующем действии. Вы можете использовать этот элемент для получения доступа к обширной информации, такой как имя действия, возвращаемый тип, пользовательские атрибуты и т.д. Точно так же вы можете использовать элемент ParameterDescriptor для изучения ожидаемых параметров данного API.

clip_image001
Рис.1. Диаграмма класса ApiDescription

Давайте посмотрим, как мы можем сгенерировать страницу помощи.

Генерация страницы помощи по API

Для простоты я буду считать, что мы используем наш REST-сервис вместе с ASP.NET MVC. Другие идеи по реализации вы можете посмотреть ниже в разделе "Другие реализации".

Пример

Для примера я использую стандартный шаблон "Web API".

clip_image002
Рис.2. Выбор проекта Web API

По умолчанию это шаблон содержит MVC-контроллер HomeController и Web API ValuesController. Давайте поменяем действие Index в HomeController для того, чтобы отобразить нашу страницу помощи.

Шаг 1. Используем ApiExplorer в представлении

Добавим следующие две строки кода в действие Index:

public ActionResult Index()
{
    var apiExplorer = GlobalConfiguration.Configuration.Services.GetApiExplorer();
    return View(apiExplorer);
}

Шаг 2. Настроим представление для отображения API

В Index.cshtml мы можем указать типом модели IApiExplorer:

@model System.Web.Http.Description.IApiExplorer

Затем мы можем пройтись по всем элементам Model.ApiDescriptions для отображения поддерживаемых HTTP-методов, относительных URL, описания действий и ожидаемых параметров.

@foreach (var api in Model.ApiDescriptions)
{
    <li>
    <h5>@api.HttpMethod @api.RelativePath</h5>
    <blockquote>
    <p>@api.Documentation</p>
    @if (api.ParameterDescriptions.Count > 0)
    {
        <h6>Parameters</h6>
        <ul>
        @foreach (var parameter in api.ParameterDescriptions)
        {
            <li>@parameter.Name: @parameter.Documentation (@parameter.Source)</li>
        }
        </ul>
    }
    </blockquote>
    </li>
}

Конечно вы можете настроить свою HTML-разметку так как вам захочется. Ниже полный код для представления:

@model System.Web.Http.Description.IApiExplorer 
<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>APIs</h3>
        <ul>
        @foreach (var api in Model.ApiDescriptions) 
        { 
            <li>
            <h5>@api.HttpMethod @api.RelativePath</h5>
            <blockquote>
            <p>@api.Documentation</p>
            @if (api.ParameterDescriptions.Count > 0) 
            { 
                <h6>Parameters</h6>
                <ul>
                @foreach (var parameter in api.ParameterDescriptions) 
                { 
                    <li>@parameter.Name: @parameter.Documentation (@parameter.Source)</li>
                } 
                </ul>
            } 
            </blockquote>
            </li>
        } 
        </ul>
    </section>
</div>

Теперь, после запуска приложения вы должны увидеть следующую страницу с описанием доступного REST API (рисунок 3).

clip_image003
Рис.3. Страница с веб-документацией

Если вы взгляните внимательнее, описание API просто говорит "Documentation for XYZ", что естественно не очень полезно. Давайте добавим немного полезной информации.

Шаг 3. Добавление документации

Во время генерации документации для API наш ApiExplorer запрашивает IDocumentationProvider для предоставления необходимой информации. IDocumentationProvider — это абстрактный механизм, который позволяет вам определять собственный способ получения информации для документирования действий API. Это позволяет вам реализовать собственный IDocumentationProvider, который будет извлекать информацию из нужного вам источника. Ниже вы можете найти пример реализации IDocumentationProvider, которая извлекает информацию из документирующих комментариев C#.

public class XmlCommentDocumentationProvider : IDocumentationProvider 
{
    XPathNavigator _documentNavigator;
    private const string _methodExpression = "/doc/members/member[@name='M:{0}']";
    private static Regex nullableTypeNameRegex = new Regex(@"(.*.Nullable)" + Regex.Escape("`1[[") + "([^,]*),.*");

    public XmlCommentDocumentationProvider(string documentPath)
    {
        XPathDocument xpath = new XPathDocument(documentPath);
        _documentNavigator = xpath.CreateNavigator();
    }

    public virtual string GetDocumentation(HttpParameterDescriptor parameterDescriptor)
    {
        ReflectedHttpParameterDescriptor reflectedParameterDescriptor = parameterDescriptor as ReflectedHttpParameterDescriptor;
        if (reflectedParameterDescriptor != null)
        {
            XPathNavigator memberNode = GetMemberNode(reflectedParameterDescriptor.ActionDescriptor);
            if (memberNode != null)
            {
                string parameterName = reflectedParameterDescriptor.ParameterInfo.Name;
                XPathNavigator parameterNode = memberNode.SelectSingleNode(string.Format("param[@name='{0}']", parameterName));
                if (parameterNode != null)
                {
                    return parameterNode.Value.Trim();
                }
            }
        }

        return "No Documentation Found.";
    }

    public virtual string GetDocumentation(HttpActionDescriptor actionDescriptor)
    {
        XPathNavigator memberNode = GetMemberNode(actionDescriptor);
        if (memberNode != null)
        {
            XPathNavigator summaryNode = memberNode.SelectSingleNode("summary");
            if (summaryNode != null)
            {
                return summaryNode.Value.Trim();
            }
        }

        return "No Documentation Found.";
    }

    private XPathNavigator GetMemberNode(HttpActionDescriptor actionDescriptor)
    {
        ReflectedHttpActionDescriptor reflectedActionDescriptor = actionDescriptor as ReflectedHttpActionDescriptor;
        if (reflectedActionDescriptor != null)
        {
            string selectExpression = string.Format(_methodExpression, GetMemberName(reflectedActionDescriptor.MethodInfo));
            XPathNavigator node = _documentNavigator.SelectSingleNode(selectExpression);
            if (node != null)
            {
                return node;
            }
        }

        return null;
    }

    private static string GetMemberName(MethodInfo method)
    {
        string name = string.Format("{0}.{1}", method.DeclaringType.FullName, method.Name);
        var parameters = method.GetParameters();
        if (parameters.Length != 0)
        {
            string[] parameterTypeNames = parameters.Select(param => ProcessTypeName(param.ParameterType.FullName)).ToArray();
            name += string.Format("({0})", string.Join(",", parameterTypeNames));
        }

        return name;
    }

    private static string ProcessTypeName(string typeName)
    {
        //handle nullable
        var result = nullableTypeNameRegex.Match(typeName);
        if (result.Success)
        {
            return string.Format("{0}{{{1}}}", result.Groups[1].Value, result.Groups[2].Value);
        }
        return typeName;
    }
}

После этого вам необходимо подключить ваш собственный IDocumentationProvider. Простейшим способом это сделать является конфигурирование через HttpConfiguration. Обратите внимание, что XmlCommentDocumentationProvider должен знать где находится файл XML с комментариями.

var config = GlobalConfiguration.Configuration;
config.Services.Replace(typeof(IDocumentationProvider), 
    new XmlCommentDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/MyApp.xml")));

Вы можете убедиться в том, что генерация вашего файла XML документации включена в настройках проекта (рисунок 4).

clip_image004
Рис.4. Установка параметра генерации файла документации

После этого убедитесь, что ваш код содержит комментарии документирования:

clip_image005

В заключении, запустите ваш проект снова и убедитесь, что документация из вашего кода теперь доступна на странице веб-документации вашего REST API (рисунок 5).

clip_image006
Рис.5. Страница веб-документации с описанием методов

Исключение контроллера/действия из документации

По некоторым причинам может понадобиться исключить контроллер или действие из генерации документации API. Для этого вы можете воспользоваться специальным атрибутом ApiExplorerSettingsAttribute:

public class ValuesController : ApiController 
{
    [ApiExplorerSettings(IgnoreApi = true)]
    public void MySpecialAction()
    {
    }
}

Он может применяться и для контроллера:

[ApiExplorerSettings(IgnoreApi = true)]
public class MySpecialController : ApiController 
{

Другие реализации

То что я показал выше — это всего лишь один путь для реализации страницы веб-документации. Но могут быть и многие другие пути, вот, для примера, другая идея:

  • Создайте свою реализацию ApiController (например HelpController). Внутри контроллера у вас будет действие GET, которое возвращает информацию об API (в каком угодно формате). Внутри же HelpController будет использовать ApiExplorer и всю доступную через него информацию. Преимущество этого подхода в том, что он будет работать как в self-hosted режиме, так и в виде веб-сервиса.

Замечание переводчика

В статье описан будущий функционал, который будет добавлен в ASP.NET WebAPI. Вы можете попробовать его уже сегодня, установив необходимые пакеты из репозитория WebAPI aspnetwebstack.codeplex.com/.../353867 подробнее о том, как это можно сделать написано в отдельной статье.

Автор: XaocCPS

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js