문제

Postman is a tool that can be used to easily test restful web services.

If an Asp.Net project is using WebApi in conjunction with WebApi Help pages documentation can be automatically generated for exposed restful web services.

This autogenerated documentation is good, but it could be made better via added accessibility.

How can these technologies be combined to generate a JSON file that can be imported in Postman?

도움이 되었습니까?

해결책

Expanding on the blog post "Using ApiExplorer to export API information to PostMan, a Chrome extension for testing Web APIs" it is possible to generate a JSON file that can be imported into Postman for use in testing and documenting.

First you need to setup a a controller that can export 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()));
    }
}

and generate the proper models:

One for the 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; }
}

One for the 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; }
}

Finally a model for the 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; }
}

Now all you need to do is make a GET request to [application]api/postman and you'll have the latest restful API in a form that is readable by postman.

다른 팁

Why not use standard Swagger and use it with Postman?

  1. What is Swagger? (Rest Web API documentation and clients enabler)
  2. Importing Swagger files to Postman
  3. Use Swashbuckle NuGet package in visual studio to generate Swagger for your API (Install-Package Swashbuckle -Pre)

Bonus: This solution is supported with ASP.NET Core Rest WebAPI

You will also need to update the PostmanRequestGet.cs model to get this to work.

update as follows:-

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

Example using IActionDescriptorCollectionProvider with .net core 2.2, based on postman schema: 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);
        }
    }
}


라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top