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.