如何使用适合导入的 Web Api 帮助页面从 Web Api 2 项目生成 JSON Postman 集合
Posted
技术标签:
【中文标题】如何使用适合导入的 Web Api 帮助页面从 Web Api 2 项目生成 JSON Postman 集合【英文标题】:How to generate JSON Postman Collections from a WebApi2 project using WebApi HelpPages that are suitable for import 【发布时间】:2014-06-03 05:09:01 【问题描述】:Postman是一个可以用来轻松测试restful web服务的工具。
如果一个 Asp.Net 项目使用 WebApi 和 WebApi Help pages 可以自动为暴露的 RESTful Web 服务生成文档。
这个自动生成的文档很好,但可以通过增加可访问性使其变得更好。
如何将这些技术结合起来生成可以在 Postman 中导入的 JSON 文件?
【问题讨论】:
【参考方案1】:扩展博客文章“Using ApiExplorer to export API information to PostMan, a Chrome extension for testing Web APIs”,可以生成一个 JSON 文件,该文件可以导入 Postman 以用于测试和记录。
首先你需要设置一个可以导出 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;
现在您需要做的就是向 [application]api/postman 发出一个 GET 请求,然后您就会以邮递员可读的形式获得最新的 RESTful API。
【讨论】:
GetHelpPageSampleGenerator()
来自哪里?有 NuGet 包吗?我找到了一个潜在的候选人here。
正确,我使用的是 Microsoft ASP.NET Web API 2.2 帮助页面 nuget.org/packages/Microsoft.AspNet.WebApi.Help 页面
我不得不稍作改动,我在PostmanCollectionForController()
中得到了 NullReferenceException。我删除了这种类型:.ThenBy(description => description.Documentation.ToString(CultureInfo.InvariantCulture))
.Help 的 MS ASP.net nuget 包页面不存在,但其父级存在; nuget.org/packages/Microsoft.AspNet.WebApi.
@AnneTheAgile 恐怕自从我最初发布解决方案以来,Postman JSON 模式已经发生了重大变化。我已经给开发人员发了一些电子邮件,当时他并不打算发布架构。 GarDavis 别人拿了我提供的解决方案并创建了一个 NuGet 包,我不知道它目前处于什么状态,但可能适合原始解决方案的需求。【参考方案2】:
为什么不使用标准的 Swagger 并将其与 Postman 一起使用?
什么是Swagger? (Rest Web API 文档和客户端启用程序) Importing Swagger files to Postman 在 Visual Studio 中使用 Swashbuckle NuGet 包为您的 API 生成 Swagger(Install-Package Swashbuckle -Pre)
奖励:ASP.NET Core Rest WebAPI 支持此 solution
【讨论】:
在回答问题时,虽然我可能弄错了,但 Postman 并没有阅读 swagger 文件。尽管我将其用作我在 ASP.NET 4.x 中开发的几个 Web API 项目之一的当前方法。即便如此,大摇大摆的方法也需要使用具有可能不兼容许可证的其他库。同样,Swashbuckle 实现,即使它开箱即用的功能可能更丰富,也没有提供与上述解决方案一样细粒度/低级别的控制,其中包含 Swagger 规范中未涵盖的 Postman 特定功能。 对某些人来说已经足够好了,所以它可以作为未来参考的答案,包括我自己。【参考方案3】:您还需要更新 PostmanRequestGet.cs 模型以使其正常工作。
更新如下:-
/// <summary>
/// the id of the collection that the request object belongs to
/// </summary>
[JsonProperty(PropertyName = "collectionId")]
public Guid CollectionId get; set;
【讨论】:
我认为这应该是对您所指答案的评论【参考方案4】:使用IActionDescriptorCollectionProvider
和.net core 2.2 的示例,基于邮递员架构: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);
【讨论】:
以上是关于如何使用适合导入的 Web Api 帮助页面从 Web Api 2 项目生成 JSON Postman 集合的主要内容,如果未能解决你的问题,请参考以下文章
Web API 2 帮助页面是不是没有处理 XML 文档标签?
使用 Owin 时无法使 ASP.NET Web API 2 帮助页面正常工作