【问题标题】:How to generate JSON Postman Collections from a WebApi2 project using WebApi HelpPages that are suitable for import如何使用适合导入的 Web Api 帮助页面从 Web Api 2 项目生成 JSON Postman 集合
【发布时间】:2014-06-03 05:09:01
【问题描述】:

Postman是一个可以用来轻松测试restful web服务的工具。

如果一个 Asp.Net 项目使用 WebApiWebApi Help pages 可以自动为暴露的 RESTful Web 服务生成文档。

这个自动生成的文档很好,但可以通过增加可访问性使其变得更好。

如何将这些技术结合起来生成可以在 Postman 中导入的 JSON 文件?

【问题讨论】:

    标签: c# asp.net-web-api asp.net-web-api2 asp.net-web-api-helppages postman


    【解决方案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 =&gt; description.Documentation.ToString(CultureInfo.InvariantCulture))
    • .Help 的 MS ASP.net nuget 包页面不存在,但其父级存在; nuget.org/packages/Microsoft.AspNet.WebApi.
    • @AnneTheAgile 恐怕自从我最初发布解决方案以来,Postman JSON 模式已经发生了重大变化。我已经给开发人员发了一些电子邮件,当时他并不打算发布架构。 GarDavis 别人拿了我提供的解决方案并创建了一个 NuGet 包,我不知道它目前处于什么状态,但可能适合原始解决方案的需求。
    【解决方案2】:

    为什么不使用标准的 Swagger 并将其与 Postman 一起使用?

    1. 什么是Swagger? (Rest Web API 文档和客户端启用程序)
    2. Importing Swagger files to Postman
    3. 在 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);
            }
        }
    }
    
    
    

    【讨论】:

      猜你喜欢
      • 2014-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-07
      • 1970-01-01
      • 2018-01-15
      相关资源
      最近更新 更多