How to generate JSON Postman Collections from a WebApi2 project using WebApi HelpPages that are suitable for import
Asked Answered
B

4

12

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?

Burkey answered 18/4, 2014 at 16:44 Comment(0)
B
14

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.

Burkey answered 18/4, 2014 at 16:44 Comment(7)
Where does GetHelpPageSampleGenerator() come from? Is there a NuGet package? I found a potential candidate here.Exine
Correct, I'm using Microsoft ASP.NET Web API 2.2 Help Page nuget.org/packages/Microsoft.AspNet.WebApi.Help pageBurkey
I had to make a slight change, I was getting a NullReferenceException in PostmanCollectionForController(). I removed this sort: .ThenBy(description => description.Documentation.ToString(CultureInfo.InvariantCulture))Exine
The MS ASP.net nuget package page for .Help does not exist but its parent does; nuget.org/packages/Microsoft.AspNet.WebApi .Ammoniacal
Import failed due to missing collectionId. I changed this in the model to get the import to work: [JsonProperty(PropertyName = "collection-id")] to [JsonProperty(PropertyName = "collectionId")]. However the folders are created but empty and the requests are below the folders instead of inside the appropriate folders. The Json looks correct, however as far as I can tell.Pyjamas
I see there is a NuGet package for this code that has been updated. Postman.WebApi.HelpDocumentation.Pyjamas
@Ammoniacal I'm afraid the Postman JSON schema has changed significantly since I originally posted the solution. I've emailed the developer a bit, and at the time he was not intending to publish the schema. GarDavis Someone else took the solution I provided and created a NuGet package, I don't know what state it is presently in but may fit the needs of the original solution.Burkey
D
5

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

Department answered 9/12, 2016 at 9:38 Comment(2)
At the time of answering the question, though I may be mistaken, Postman did not read the swagger files. Though I use this as my current approach for one of my several Web API projects developed in ASP.NET 4.x. Even so a swagger approach requires the use of additional libraries with possibly incompatible license. Likewise the Swashbuckle implementation, even if it may be more functionally rich out of the box, does not provide as fine grained / low level control as the above mentioned solution with Postman specific features that are not covered in the swagger specification.Burkey
For some it is good enough so is it present as an answer for future references including myself.Department
S
1

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; }
Shrieval answered 11/2, 2016 at 12:22 Comment(1)
i think this should be a comment to the answer you're referring toVerdure
R
1

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);
        }
    }
}


Ralaigh answered 18/7, 2020 at 21:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.