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?

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
rheone
  • 1,517
  • 1
  • 17
  • 30

4 Answers4

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.

rheone
  • 1,517
  • 1
  • 17
  • 30
  • Where does `GetHelpPageSampleGenerator()` come from? Is there a NuGet package? I found a potential candidate [here](https://github.com/flextry/Telerik-Academy/tree/master/Programming%20with%20C%23/0.%20Exams/Telerik%202013-2014%20-%20Web%20Services%20%26%20Cloud/Web%20Services%20%26%20Cloud%20-%20Exam%20Preparation/ForumSystem.Web/Areas/HelpPage/SampleGeneration). – mason Dec 05 '14 at 17:12
  • Correct, I'm using Microsoft ASP.NET Web API 2.2 Help Page https://www.nuget.org/packages/Microsoft.AspNet.WebApi.Help page – rheone Dec 05 '14 at 17:19
  • 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))` – mason Dec 05 '14 at 17:42
  • The MS ASP.net nuget package page for .Help does not exist but its parent does; https://www.nuget.org/packages/Microsoft.AspNet.WebApi . – AnneTheAgile Dec 23 '15 at 02:08
  • 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. – GarDavis Apr 12 '16 at 17:03
  • I see there is a NuGet package for this code that has been updated. Postman.WebApi.HelpDocumentation. – GarDavis Apr 12 '16 at 18:59
  • 1
    @AnneTheAgile 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. – rheone Apr 12 '16 at 19:11
4

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

hB0
  • 1,977
  • 1
  • 27
  • 33
  • 1
    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. – rheone Dec 09 '16 at 14:36
  • For some it is good enough so is it present as an answer for future references including myself. – hB0 Dec 10 '16 at 15:01
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; }
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);
        }
    }
}


Nicolae Lupei
  • 161
  • 2
  • 5