Как создавать коллекции JSON Postman из проекта WebApi2 с помощью справочных страниц WebApi, подходящих для импорта

Postman – это инструмент, который можно использовать для простого тестирования веб-сервисов для отдыха.

Если проект Asp.Net использует WebApi в сочетании с Страницы справки WebApi могут автоматически создаваться для открытых веб-сайтов Сервисы.

Эта автоматически сгенерированная документация хороша, но ее можно было бы улучшить, добавив специальные возможности.

Как можно объединить эти технологии для создания файла JSON, который можно импортировать в Postman?


person rheone    schedule 18.04.2014    source источник


Ответы (4)


Расширение сообщения в блоге "Используя ApiExplorer для экспорта информации API в PostMan, расширение Chrome для тестирования веб-API", можно создать файл JSON, который можно импортировать в Почтальон для использования в тестировании и документировании.

Сначала вам нужно настроить контроллер, который может экспортировать JSON.

/// <summary>
///     Based on
///     http://blogs.msdn.com/b/yaohuang1/archive/2012/06/15/using-apiexplorer-to-export-api-information-to-postman-a-chrome-extension-for-testing-web-apis.aspx
/// </summary>
[RoutePrefix("api/postman")]
public class PostmanApiController : ApiController
{
    /// <summary>
    ///     Produce [POSTMAN](http://www.getpostman.com) related responses
    /// </summary>
    public PostmanApiController()
    {
        // exists for documentation purposes
    }

    private readonly Regex _pathVariableRegEx = new Regex("\\{([A-Za-z0-9-_]+)\\}", RegexOptions.ECMAScript | RegexOptions.Compiled);
    private readonly Regex _urlParameterVariableRegEx = new Regex("=\\{([A-Za-z0-9-_]+)\\}", RegexOptions.ECMAScript | RegexOptions.Compiled);

    /// <summary>
    ///     Get a postman collection of all visible Api
    ///     (Get the [POSTMAN](http://www.getpostman.com) chrome extension)
    /// </summary>
    /// <returns>object describing a POSTMAN collection</returns>
    /// <remarks>Get a postman collection of all visible api</remarks>
    [HttpGet]
    [Route(Name = "GetPostmanCollection")]
    [ResponseType(typeof (PostmanCollectionGet))]
    public IHttpActionResult GetPostmanCollection()
    {
        return Ok(this.PostmanCollectionForController());
    }

    private PostmanCollectionGet PostmanCollectionForController()
    {
        var requestUri = Request.RequestUri;
        var baseUri = requestUri.Scheme + "://" + requestUri.Host + ":" + requestUri.Port
                      + HttpContext.Current.Request.ApplicationPath;

        var postManCollection = new PostmanCollectionGet
                                {
                                    Id = Guid.NewGuid(),
                                    Name = "[Name of your API]",
                                    Timestamp = DateTime.Now.Ticks,
                                    Requests = new Collection<PostmanRequestGet>(),
                                    Folders = new Collection<PostmanFolderGet>(),
                                    Synced = false,
                                    Description = "[Description of your API]"
                                };


        var helpPageSampleGenerator = Configuration.GetHelpPageSampleGenerator();

        var apiExplorer = Configuration.Services.GetApiExplorer();

        var apiDescriptionsByController = apiExplorer.ApiDescriptions.GroupBy(
            description =>
            description.ActionDescriptor.ActionBinding.ActionDescriptor.ControllerDescriptor.ControllerType);

        foreach (var apiDescriptionsByControllerGroup in apiDescriptionsByController)
        {
            var controllerName = apiDescriptionsByControllerGroup.Key.Name.Replace("Controller", string.Empty);

            var postManFolder = new PostmanFolderGet
                                {
                                    Id = Guid.NewGuid(),
                                    CollectionId = postManCollection.Id,
                                    Name = controllerName,
                                    Description = string.Format("Api Methods for {0}", controllerName),
                                    CollectionName = "api",
                                    Order = new Collection<Guid>()
                                };

            foreach (var apiDescription in apiDescriptionsByControllerGroup
                .OrderBy(description => description.HttpMethod, new HttpMethodComparator())
                .ThenBy(description => description.RelativePath)
                .ThenBy(description => description.Documentation.ToString(CultureInfo.InvariantCulture)))
            {
                TextSample sampleData = null;
                var sampleDictionary = helpPageSampleGenerator.GetSample(apiDescription, SampleDirection.Request);
                MediaTypeHeaderValue mediaTypeHeader;
                if (MediaTypeHeaderValue.TryParse("application/json", out mediaTypeHeader)
                    && sampleDictionary.ContainsKey(mediaTypeHeader))
                {
                    sampleData = sampleDictionary[mediaTypeHeader] as TextSample;
                }

                // scrub curly braces from url parameter values
                var cleanedUrlParameterUrl = this._urlParameterVariableRegEx.Replace(apiDescription.RelativePath, "=$1-value");

                // get pat variables from url
                var pathVariables = this._pathVariableRegEx.Matches(cleanedUrlParameterUrl)
                                        .Cast<Match>()
                                        .Select(m => m.Value)
                                        .Select(s => s.Substring(1, s.Length - 2))
                                        .ToDictionary(s => s, s => string.Format("{0}-value", s));

                // change format of parameters within string to be colon prefixed rather than curly brace wrapped
                var postmanReadyUrl = this._pathVariableRegEx.Replace(cleanedUrlParameterUrl, ":$1");

                // prefix url with base uri
                var url = baseUri.TrimEnd('/') + "/" + postmanReadyUrl;

                var request = new PostmanRequestGet
                              {
                                  CollectionId = postManCollection.Id,
                                  Id = Guid.NewGuid(),
                                  Name = apiDescription.RelativePath,
                                  Description = apiDescription.Documentation,
                                  Url = url,
                                  Method = apiDescription.HttpMethod.Method,
                                  Headers = "Content-Type: application/json",
                                  Data = sampleData == null
                                             ? null
                                             : sampleData.Text,
                                  DataMode = "raw",
                                  Time = postManCollection.Timestamp,
                                  Synced = false,
                                  DescriptionFormat = "markdown",
                                  Version = "beta",
                                  Responses = new Collection<string>(),
                                  PathVariables = pathVariables
                              };

                postManFolder.Order.Add(request.Id); // add to the folder
                postManCollection.Requests.Add(request);
            }

            postManCollection.Folders.Add(postManFolder);
        }

        return postManCollection;
    }
}

/// <summary>
///     Quick comparer for ordering http methods for display
/// </summary>
internal class HttpMethodComparator : IComparer<HttpMethod>
{
    private readonly string[] _order =
    {
        "GET",
        "POST",
        "PUT",
        "DELETE"
    };

    public int Compare(HttpMethod x, HttpMethod y)
    {
        return Array.IndexOf(this._order, x.ToString()).CompareTo(Array.IndexOf(this._order, y.ToString()));
    }
}

и создайте правильные модели:

Один для PostManCollection

/// <summary>
///     [Postman](http://getpostman.com) collection representation
/// </summary>
public class PostmanCollectionGet
{
    /// <summary>
    ///     Id of collection
    /// </summary>
    [JsonProperty(PropertyName = "id")]
    public Guid Id { get; set; }

    /// <summary>
    ///     Name of collection
    /// </summary>
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    /// <summary>
    ///     Collection generation time
    /// </summary>
    [JsonProperty(PropertyName = "timestamp")]
    public long Timestamp { get; set; }

    /// <summary>
    ///     Requests associated with the collection
    /// </summary>
    [JsonProperty(PropertyName = "requests")]
    public ICollection<PostmanRequestGet> Requests { get; set; }

    /// <summary>
    ///     **unused always false**
    /// </summary>
    [JsonProperty(PropertyName = "synced")]
    public bool Synced { get; set; }

    /// <summary>
    ///     folders within the collection
    /// </summary>
    [JsonProperty(PropertyName = "folders")]
    public ICollection<PostmanFolderGet> Folders { get; set; }

    /// <summary>
    ///     Description of collection
    /// </summary>
    [JsonProperty(PropertyName = "description")]
    public string Description { get; set; }
}

Один для PostmanFolder

/// <summary>
///     Object that describes a [Postman](http://getpostman.com) folder
/// </summary>
public class PostmanFolderGet
{
    /// <summary>
    ///     id of the folder
    /// </summary>
    [JsonProperty(PropertyName = "id")]
    public Guid Id { get; set; }

    /// <summary>
    ///     folder name
    /// </summary>
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    /// <summary>
    ///     folder description
    /// </summary>
    [JsonProperty(PropertyName = "description")]
    public string Description { get; set; }

    /// <summary>
    ///     ordered list of ids of items in folder
    /// </summary>
    [JsonProperty(PropertyName = "order")]
    public ICollection<Guid> Order { get; set; }

    /// <summary>
    ///     Name of the collection
    /// </summary>
    [JsonProperty(PropertyName = "collection_name")]
    public string CollectionName { get; set; }

    /// <summary>
    ///     id of the collection
    /// </summary>
    [JsonProperty(PropertyName = "collection_id")]
    public Guid CollectionId { get; set; }
}

Наконец, модель для PostmanRequest

/// <summary>
///     [Postman](http://getpostman.com) request object
/// </summary>
public class PostmanRequestGet
{
    /// <summary>
    ///     id of request
    /// </summary>
    [JsonProperty(PropertyName = "id")]
    public Guid Id { get; set; }

    /// <summary>
    ///     headers associated with the request
    /// </summary>
    [JsonProperty(PropertyName = "headers")]
    public string Headers { get; set; }

    /// <summary>
    ///     url of the request
    /// </summary>
    [JsonProperty(PropertyName = "url")]
    public string Url { get; set; }

    /// <summary>
    ///     path variables of the request
    /// </summary>
    [JsonProperty(PropertyName = "pathVariables")]
    public Dictionary<string, string> PathVariables { get; set; }

    /// <summary>
    ///     method of request
    /// </summary>
    [JsonProperty(PropertyName = "method")]
    public string Method { get; set; }

    /// <summary>
    ///     data to be sent with the request
    /// </summary>
    [JsonProperty(PropertyName = "data")]
    public string Data { get; set; }

    /// <summary>
    ///     data mode of reqeust
    /// </summary>
    [JsonProperty(PropertyName = "dataMode")]
    public string DataMode { get; set; }

    /// <summary>
    ///     name of request
    /// </summary>
    [JsonProperty(PropertyName = "name")]
    public string Name { get; set; }

    /// <summary>
    ///     request description
    /// </summary>
    [JsonProperty(PropertyName = "description")]
    public string Description { get; set; }

    /// <summary>
    ///     format of description
    /// </summary>
    [JsonProperty(PropertyName = "descriptionFormat")]
    public string DescriptionFormat { get; set; }

    /// <summary>
    ///     time that this request object was generated
    /// </summary>
    [JsonProperty(PropertyName = "time")]
    public long Time { get; set; }

    /// <summary>
    ///     version of the request object
    /// </summary>
    [JsonProperty(PropertyName = "version")]
    public string Version { get; set; }

    /// <summary>
    ///     request response
    /// </summary>
    [JsonProperty(PropertyName = "responses")]
    public ICollection<string> Responses { get; set; }

    /// <summary>
    ///     the id of the collection that the request object belongs to
    /// </summary>
    [JsonProperty(PropertyName = "collection-id")]
    public Guid CollectionId { get; set; }

    /// <summary>
    ///     Synching
    /// </summary>
    [JsonProperty(PropertyName = "synced")]
    public bool Synced { get; set; }
}

Теперь все, что вам нужно сделать, это сделать запрос GET к [application]api/postman, и вы получите последний restful API в форме, которую почтальон может прочитать.

person rheone    schedule 18.04.2014
comment
Откуда GetHelpPageSampleGenerator()? Есть ли пакет NuGet? Я нашел потенциального кандидата здесь. - person mason; 05.12.2014
comment
Правильно, я использую страницу справки Microsoft ASP.NET Web API 2.2 nuget.org Страница /packages/Microsoft.AspNet.WebApi.Help - person rheone; 05.12.2014
comment
Мне пришлось внести небольшое изменение, я получал исключение NullReferenceException в PostmanCollectionForController(). Я удалил этот вид: .ThenBy(description => description.Documentation.ToString(CultureInfo.InvariantCulture)) - person mason; 05.12.2014
comment
Страница пакета MS ASP.net nuget для .Help не существует, но существует ее родитель; nuget.org/packages/Microsoft.AspNet.WebApi . - person AnneTheAgile; 23.12.2015
comment
Не удалось импортировать из-за отсутствия идентификатора коллекции. Я изменил это в модели, чтобы заставить импорт работать: [JsonProperty(PropertyName = collection-id)] на [JsonProperty(PropertyName = collectionId)]. Однако папки созданы, но пусты, а запросы находятся под папками, а не внутри соответствующих папок. Насколько я могу судить, Json выглядит правильно. - person GarDavis; 12.04.2016
comment
Я вижу, что для этого кода есть обновленный пакет NuGet. Postman.WebApi.HelpДокументация. - person GarDavis; 12.04.2016
comment
@AnneTheAgile Боюсь, схема Postman JSON значительно изменилась с тех пор, как я изначально опубликовал решение. Я немного написал разработчику по электронной почте, и в то время он не собирался публиковать схему. GarDavis Кто-то другой взял решение, которое я предоставил, и создал пакет NuGet, я не знаю, в каком состоянии он сейчас находится, но может соответствовать потребностям исходного решения. - person rheone; 12.04.2016

Почему бы не использовать стандартный Swagger и использовать его с Postman?

  1. Что такое Swagger? (Документация по Rest Web API и активатор клиентов)
  2. Импорт файлов Swagger в Postman
  3. Используйте пакет NuGet Swashbuckle в Visual Studio для создания Swagger для вашего API (Install-Package Swashbuckle -Pre)

Бонус: это решение поддерживается с помощью ASP.NET Core Rest WebAPI

person hB0    schedule 09.12.2016
comment
Во время ответа на вопрос, хотя я могу ошибаться, Постман не читал файлы чванства. Хотя я использую это как мой текущий подход для одного из моих нескольких проектов веб-API, разработанных в ASP.NET 4.x. Даже в этом случае подход чванства требует использования дополнительных библиотек с возможно несовместимой лицензией. Точно так же реализация Swashbuckle, даже если она может быть более функционально богатой из коробки, не обеспечивает такого мелкозернистого / низкоуровневого управления, как вышеупомянутое решение со специфическими функциями Postman, которые не рассматриваются в спецификации swagger. - person rheone; 09.12.2016
comment
Для некоторых это достаточно хорошо, так что это ответ на будущие ссылки, включая меня. - person hB0; 10.12.2016

Вам также потребуется обновить модель PostmanRequestGet.cs, чтобы это заработало.

обновить следующим образом: -

 /// <summary>
        ///     the id of the collection that the request object belongs to
        /// </summary>
        [JsonProperty(PropertyName = "collectionId")]
        public Guid CollectionId { get; set; }
person Martin Levington    schedule 11.02.2016
comment
я думаю, что это должен быть комментарий к ответу, на который вы ссылаетесь - person Paolo Fulgoni; 11.08.2016

Пример использования IActionDescriptorCollectionProvider с .net core 2.2 на основе схемы postman: https://schema.getpostman.com/json/collection/v2.0.0/collection.json


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using GR.Core.Extensions;
using GR.Core.Razor.Attributes;
using GR.Core.Razor.Models.PostmanModels;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;

namespace GR.Core.Razor.Api
{
    [AllowAnonymous]
    [Route("/postman")]
    public class PostmanDocsApiController : Controller
    {
        #region Injectable

        /// <summary>
        /// Inject action descriptor service
        /// </summary>
        private readonly IActionDescriptorCollectionProvider _provider;

        #endregion

        public PostmanDocsApiController(IActionDescriptorCollectionProvider provider)
        {
            _provider = provider;
        }

        /// <summary>
        /// Postman collection
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [JsonProduces(typeof(PostmanCollection))]
        public JsonResult Docs()
        {
            var postManCollection = new PostmanCollection
            {
                Info = new PostmanInfo
                {
                    Id = Guid.NewGuid(),
                    Name = $"{GearApplication.ApplicationName} API",
                    Description = "api"
                },
                Folders = new Collection<PostmanFolder>()
            };

            var apiRoutes = _provider.ActionDescriptors.Items
                .Where(x => x.AttributeRouteInfo?.Template?.StartsWith("api/") ?? false).ToList();

            var groups = apiRoutes.GroupBy(x => x.RouteValues["Controller"])
                  .ToList();

            foreach (var group in groups)
            {
                var controllerGroup = apiRoutes.FirstOrDefault(x => x.RouteValues["Controller"].Equals(group.Key)).Is<ControllerActionDescriptor>();
                var type = controllerGroup.ControllerTypeInfo;
                var typeSummary = type.GetSummary();
                var postManFolder = new PostmanFolder
                {
                    Name = group.Key,
                    Description = typeSummary,
                    FolderRequests = new List<PostmanFolderRequest>()
                };
                var domain = new Uri(HttpContext.GetAppBaseUrl());
                foreach (var route in group)
                {
                    var constraint = route.ActionConstraints[0]?.Is<HttpMethodActionConstraint>();
                    var methodSummary = type.GetMethod(route.RouteValues["Action"]).GetSummary();
                    var methodDescriptor = route.Is<ControllerActionDescriptor>();
                    var request = new PostmanRequest
                    {
                        Url = new PostmanRequestUrl
                        {
                            Host = domain.Authority,
                            Path = route.AttributeRouteInfo.Template,
                            Protocol = HttpContext.Request.Scheme,
                            Query = new List<object>()
                        },
                        Method = constraint?.HttpMethods.FirstOrDefault() ?? "GET",
                        Headers = new List<PostmanHeader>
                        {
                            new PostmanHeader
                            {
                                Key = "Content-Type",
                                Value = "application/json"
                            }
                        },
                        Responses = new Collection<object>(),
                        Description = methodSummary,
                    };

                    var inputDictionary = methodDescriptor.Parameters.ToList()
                        .ToDictionary(parameter => parameter.Name, parameter => parameter.ParameterType.GetDefault());

                    request.Body = new PostmanBodyRequest
                    {
                        Mode = "raw",
                        Raw = inputDictionary.SerializeAsJson()
                    };

                    postManFolder.FolderRequests.Add(new PostmanFolderRequest
                    {
                        Name = route.RouteValues["Action"],
                        Request = request
                    });
                }

                postManCollection.Folders.Add(postManFolder);
            }

            return Json(postManCollection);
        }
    }
}


person Nicolae Lupei    schedule 18.07.2020