Customize MapHttpAttributeRoutes for Web Api Versioning

后端 未结 3 705
感动是毒
感动是毒 2021-02-10 16:52

I am implementing Web API versioning as in Web API Versioning. My controllers are in 2 separate namespaces, and I\'ve used a custom SelectController method to choose which versi

3条回答
  •  轮回少年
    2021-02-10 17:20

    I extended Michael Brown's answer to allow setting a default version:

    Only now I'm thinking how to make it work with Swashbuckle swagger.

    RouteVersionAttribute:

    using System.Collections.Generic;
    using System.Web.Http.Routing;
    
    namespace YourNameSpace.Filters
    {
        /// 
        /// Here is a solution that will let you use the Web API 2 way of versioned routes (headers),
        /// in addition to query parameter support (i.e.use a header called 'api-version' or 
        /// a querystring parameter named '?api-version=XXX'.
        /// https://stackoverflow.com/a/28934352/3187389
        /// https://stackoverflow.com/questions/25299889/customize-maphttpattributeroutes-for-web-api-versioning
        /// 
        public class RouteVersionAttribute : RouteFactoryAttribute
        {
            public int Version { get; private set; }
            public int VersionDefault { get; private set; }
    
            public RouteVersionAttribute() : this(null, 1, true)
            {
            }
    
            /// 
            /// Specify a version for the WebAPI controller or an action method
            /// for example: [RouteVersion("Test", 1)] or [RouteVersion("Test", 1, true)]
            /// 
            /// 
            /// 
            public RouteVersionAttribute(int version, bool isDefault = false) : this(null, version, isDefault)
            {
            }
    
            /// 
            /// Specify a version for the WebAPI controller or an action method
            /// for example: [RouteVersion("Test", 1)] or [RouteVersion("Test", 1, true)]
            /// 
            /// 
            /// 
            /// 
            public RouteVersionAttribute(string template, int version, bool isDefault = false)
                : base(template)
            {
                Version = version;
                if (isDefault)
                    VersionDefault = version;
            }
    
            public override IDictionary Constraints
            {
                get
                {
                    var constraints = new HttpRouteValueDictionary();
                    constraints.Add("version", new RouteVersionHttpConstraint(Version, VersionDefault));
                    return constraints;
                }
            }
    
            public override IDictionary Defaults
            {
                get
                {
                    var defaults = new HttpRouteValueDictionary();
                    defaults.Add("version", VersionDefault);
                    return defaults;
                }
            }
        }
    }
    

    RouteVersionHttpConstraint:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Net.Http;
    using System.Web.Http.Routing;
    
    namespace Boyd.Core.Filters
    {
        /// 
        /// Here is a solution that will let you use the Web API 2 way of versioned routes (headers),
        /// in addition to query parameter support (i.e.use a header called 'api-version' or 
        /// a querystring parameter named '?api-version=XXX'.
        /// https://stackoverflow.com/a/28934352/3187389
        /// https://stackoverflow.com/questions/25299889/customize-maphttpattributeroutes-for-web-api-versioning
        /// 
        public class RouteVersionHttpConstraint : IHttpRouteConstraint
        {
            public const string VersionHeaderName = "api-version";
            private readonly int VersionDefault = 1;
    
            /// 
            /// Add a route constraint to detect version header or by query string
            /// 
            /// 
            public RouteVersionHttpConstraint(int allowedVersion, int versionDefault)
            {
                AllowedVersion = allowedVersion;
                VersionDefault = versionDefault;
            }
    
            public int AllowedVersion
            {
                get;
                private set;
            }
    
            /// 
            /// Perform the controller match
            /// 
            /// 
            /// 
            /// 
            /// 
            /// 
            /// 
            public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary values, HttpRouteDirection routeDirection)
            {
                if (routeDirection == HttpRouteDirection.UriResolution)
                {
                    int version = GetVersionHeaderOrQuery(request) ?? VersionDefault;
                    if (version == AllowedVersion)
                    {
                        return true;
                    }
                }
                return false;
            }
    
            /// 
            /// Check the request header, and the query string to determine if a version number has been provided
            /// 
            /// 
            /// 
            private int? GetVersionHeaderOrQuery(HttpRequestMessage request)
            {
                string versionAsString;
                if (request.Headers.TryGetValues(VersionHeaderName, out IEnumerable headerValues) 
                    && headerValues.Count() == 1)
                {
                    versionAsString = headerValues.First();
                    if (versionAsString != null && Int32.TryParse(versionAsString, out int version))
                    {
                        return version;
                    }
                }
                else
                {
                    var query = System.Web.HttpUtility.ParseQueryString(request.RequestUri.Query);
                    string versionStr = query[VersionHeaderName];
                    int.TryParse(versionStr, out int version);
                    if (version > 0)
                        return version;
                }
                return null;
            }
        }
    }
    

    Usage (can be used on the controller or action methods):

    #region Temporary Tests
    
    // {{BaseUrl}}Test?api-version=1
    
    [HttpGet]
    [RouteVersion("Test", 1)]
    public async Task Test1([FromBody]GetCustomerW2GsForPropertyRequest request)
    {
        return await Task.FromResult(Ok("API Version 1 selected"));
    }
    
    [HttpGet]
    [RouteVersion("Test", 2)]
    [RouteVersion("Test", 3)]
    [RouteVersion("Test", 4)]
    public async Task Test4([FromBody]GetCustomerW2GsForPropertyRequest request)
    {
        return await Task.FromResult(Ok("API Version 2, 3 or 4 selected"));
    }
    
    [HttpGet]
    [RouteVersion("Test", 5, true)]
    public async Task Test5([FromBody]GetCustomerW2GsForPropertyRequest request)
    {
        return await Task.FromResult(Ok("API Version 5 selected"));
    }
    
    #endregion Temporary Tests
    

提交回复
热议问题