【问题标题】:Getting Web API Help Pages to Work with Custom Routing Constraints让 Web API 帮助页面使用自定义路由约束
【发布时间】:2016-07-17 14:04:12
【问题描述】:

在我的项目中,我实现了自定义路由约束,以允许通过自定义标头变量 (api-version) 进行 API 版本控制,类似于 this sample project on Codeplex,尽管我修改了约束以允许使用 major.minor 约定。

这涉及创建两个单独的控制器,它们的路由通过 FullVersionedRoute 属性进行区分:

Sample1Controller.cs

/// <summary>
/// v1.0 Controller
/// </summary>
public class Sample1Controller : ApiController
{
    [FullVersionedRoute("api/test", "1.0")]
    public IEnumerable<string> Get()
    {
        return new[] { "This is version 1.0 test!" };
    }
}

Sample2Controller.cs

/// <summary>
/// v2.0 Controller
/// </summary>
public class Sample2Controller : ApiController
{
    [FullVersionedRoute("api/test", "2.0")]
    public IEnumerable<string> Get()
    {
        return new[] { "This is version 2.0 test!" };
    }
}

FullVersionedRoute.cs

    using System.Collections.Generic;
    using System.Web.Http.Routing;

namespace HelperClasses.Versioning
{
    /// <summary>
    /// Provides an attribute route that's restricted to a specific version of the api.
    /// </summary>
    internal class FullVersionedRoute : RouteFactoryAttribute
    {
        public FullVersionedRoute(string template, string allowedVersion) : base(template)
        {
            AllowedVersion = allowedVersion;
        }

        public string AllowedVersion
        {
            get;
            private set;
        }

        public override IDictionary<string, object> Constraints
        {
            get
            {
                var constraints = new HttpRouteValueDictionary();
                constraints.Add("version", new FullVersionConstraint(AllowedVersion));
                return constraints;
            }
        }
    }
}

FullVersionConstraint.cs

using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http.Routing;

namespace HelperClasses.Versioning
{
    /// <summary>
    /// A Constraint implementation that matches an HTTP header against an expected version value.
    /// </summary>
    internal class FullVersionConstraint : IHttpRouteConstraint
    {
        public const string VersionHeaderName = "api-version";

        private const string DefaultVersion = "1.0";

        public FullVersionConstraint(string allowedVersion)
        {
            AllowedVersion = allowedVersion;
        }

        public string AllowedVersion
        {
            get;
            private set;
        }

        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
        {
            if (routeDirection == HttpRouteDirection.UriResolution)
            {
                var version = GetVersionHeader(request) ?? DefaultVersion;
                return (version == AllowedVersion);
            }

            return false;
        }

        private string GetVersionHeader(HttpRequestMessage request)
        {
            IEnumerable<string> headerValues;

            if (request.Headers.TryGetValues(VersionHeaderName, out headerValues))
            {
                // enumerate the list once
                IEnumerable<string> headers = headerValues.ToList();

                // if we find once instance of the target header variable, return it
                if (headers.Count() == 1)
                {
                    return headers.First();
                }
            }

            return null;
        }
    }
}

这很好用,但是auto-generated help files 无法区分两个控制器中的操作,因为它们看起来像相同的路由(如果您只注意 url 路由,默认情况下会这样做)。因此,来自 Sample2Controller.cs 的操作会覆盖来自 Sample1Controller.cs 的操作,因此帮助页面上仅显示 Sample2 API。

有没有办法配置 Web API 帮助页面包以识别自定义约束并识别有两个单独的 API,然后在帮助页面上将它们显示为单独的 API 组?

【问题讨论】:

  • 我在使用自定义路由时遇到了类似的问题。我的问题是为什么不部署到 server/approot/version,将多个版本嵌套在同一个代码库中似乎有点奇怪,因为每次重新部署都是对所有版本的更改,即使逻辑中不应该有任何更改原版你不能100%
  • 根据这篇文章,这是因为该解决方案会给人一种印象,即您拥有多个不同的资源(即 v1/myresource、v2/myresource、v3/myresource)。实际上,只有一个资源(即 /myresource),只是它的不同版本(基于 Accept 标头提供服务,这是根据 HTTP SPEC 的目的)。这是一个语义论证。 troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html

标签: c# asp.net-mvc asp.net-web-api


【解决方案1】:

我找到了this article,它描述了如何通过实现 IApiExplorer 来实现这一目标。

简而言之,您要做的是添加一个新的 VersionedApiExplorer 类,像这样实现 IApiExplorer

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Description;
using System.Web.Http.Routing;

namespace HelperClasses.Versioning
{
    public class VersionedApiExplorer<TVersionConstraint> : IApiExplorer
    {
        private IApiExplorer _innerApiExplorer;
        private HttpConfiguration _configuration;
        private Lazy<Collection<ApiDescription>> _apiDescriptions;
        private MethodInfo _apiDescriptionPopulator;

        public VersionedApiExplorer(IApiExplorer apiExplorer, HttpConfiguration configuration)
        {
            _innerApiExplorer = apiExplorer;
            _configuration = configuration;
            _apiDescriptions = new Lazy<Collection<ApiDescription>>(
                new Func<Collection<ApiDescription>>(Init));
        }

        public Collection<ApiDescription> ApiDescriptions
        {
            get { return _apiDescriptions.Value; }
        }

        private Collection<ApiDescription> Init()
        {
            var descriptions = _innerApiExplorer.ApiDescriptions;

            var controllerSelector = _configuration.Services.GetHttpControllerSelector();
            var controllerMappings = controllerSelector.GetControllerMapping();

            var flatRoutes = FlattenRoutes(_configuration.Routes);
            var result = new Collection<ApiDescription>();

            foreach (var description in descriptions)
            {
                result.Add(description);

                if (controllerMappings != null && description.Route.Constraints.Any(c => c.Value is TVersionConstraint))
                {
                    var matchingRoutes = flatRoutes.Where(r => r.RouteTemplate == description.Route.RouteTemplate && r != description.Route);

                    foreach (var route in matchingRoutes)
                        GetRouteDescriptions(route, result);
                }
            }
            return result;
        }

        private void GetRouteDescriptions(IHttpRoute route, Collection<ApiDescription> apiDescriptions)
        {
            var actionDescriptor = route.DataTokens["actions"] as IEnumerable<HttpActionDescriptor>;

            if (actionDescriptor != null && actionDescriptor.Count() > 0)
                GetPopulateMethod().Invoke(_innerApiExplorer, new object[] { actionDescriptor.First(), route, route.RouteTemplate, apiDescriptions });
        }

        private MethodInfo GetPopulateMethod()
        {
            if (_apiDescriptionPopulator == null)
                _apiDescriptionPopulator = _innerApiExplorer.GetType().GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(
                   m => m.Name == "PopulateActionDescriptions" && m.GetParameters().Length == 4);

            return _apiDescriptionPopulator;
        }

        public static IEnumerable<IHttpRoute> FlattenRoutes(IEnumerable<IHttpRoute> routes)
        {
            var flatRoutes = new List<HttpRoute>();

            foreach (var route in routes)
            {
                if (route is HttpRoute)
                    yield return route;

                var subRoutes = route as IReadOnlyCollection<IHttpRoute>;
                if (subRoutes != null)
                    foreach (IHttpRoute subRoute in FlattenRoutes(subRoutes))
                        yield return subRoute;
            }
        }
    }
}

然后将其添加到您的 WebAPIConfig

var apiExplorer = config.Services.GetApiExplorer();
config.Services.Replace(typeof(IApiExplorer), new VersionedApiExplorer<FullVersionConstraint>(apiExplorer, config));

然后,您应该会在 Web API 帮助页面上看到 Sample1 和 Sample2 API。

【讨论】:

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