我正在Web API Versioning中实现Web API版本控制。我的控制器位于2个独立的命名空间中,我使用自定义SelectController方法根据查询参数选择要使用的版本。 例如
http://myapi/api/values?version=1.0
这一切都运行正常,但控制器中的某些操作使用Route属性
[Route("api/values/getNames")]
public HttpResponseMessage Get() { ... }
默认使用
映射到正确的控制器config.MapHttpAttributeRoutes();
WebApiConfig.cs中的
如果我有多个版本的API使用相同的路由,这将无效。我是否能够为config.MapHttpAttributeRoutes()提供自定义实现,以便我可以选择要使用的API的正确版本,还是有更好的方法来实现这一目标?
答案 0 :(得分:11)
在官方WebApi 2.1 examplex on Codeplex中有一个例子。 它依赖于请求标头值来存储版本。
我认为它更好,因为它允许所有版本的路由保持不变。客户端只需在请求中包含HTTP标头(在本例中为版本号)即可选择版本。
此示例显示如何使用属性路由和约束 ASP.NET Web API通过'api-version'动态过滤控制器 HTTP标头。当路由使用约束时,每个约束都有一个 有可能阻止路由匹配给定的请求。在这 sample,自定义RouteFactoryAttribute(VersionedRoute)添加一个 对每个属性路由的约束。
...
自定义约束实现(VersionConstraint)是 基于匹配整数的'api-version'的值实现 值。提供了约束的允许版本的值 通过放置在每个控制器上的VersionedRoute属性。当一个 请求进来,'api-version'的标头值匹配 预期的版本。此示例使用标头但使用约束 实现可以使用任何标准来决定请求是否 对路线有效。
无论如何,最终结果看起来像这样:
[VersionedRoute("api/Customer", 1)]
public class CustomerVersion1Controller : ApiController
{
// controller code goes here
}
[VersionedRoute("api/Customer", 2)]
public class CustomerVersion2Controller : ApiController
{
// controller code goes here
}
答案 1 :(得分:5)
这是一个解决方案,除了查询参数支持之外,还允许您使用Web API 2版本化路由(标题)(即使用名为' api-version'或查询字符串参数的标题命名'?api-version = XXX'。
HTTP Route约束可以完成工作:
/// <summary>
/// Add a route constraint to detect version header or by query string
/// </summary>
public class RouteVersionHttpConstraint : IHttpRouteConstraint
{
public const string VersionHeaderName = "api-version";
private const int DefaultVersion = 1;
/// <summary>
/// Add a route constraint to detect version header or by query string
/// </summary>
/// <param name="allowedVersion"></param>
public RouteVersionHttpConstraint(int allowedVersion)
{
AllowedVersion = allowedVersion;
}
public int AllowedVersion
{
get;
private set;
}
/// <summary>
/// Perform the controller match
/// </summary>
/// <param name="request"></param>
/// <param name="route"></param>
/// <param name="parameterName"></param>
/// <param name="values"></param>
/// <param name="routeDirection"></param>
/// <returns></returns>
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection == HttpRouteDirection.UriResolution)
{
int version = GetVersionHeaderOrQuery(request) ?? DefaultVersion;
if (version == AllowedVersion)
{
return true;
}
}
return false;
}
/// <summary>
/// Check the request header, and the query string to determine if a version number has been provided
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private int? GetVersionHeaderOrQuery(HttpRequestMessage request)
{
string versionAsString;
IEnumerable<string> headerValues;
if (request.Headers.TryGetValues(VersionHeaderName, out headerValues) && headerValues.Count() == 1)
{
versionAsString = headerValues.First();
int version;
if (versionAsString != null && Int32.TryParse(versionAsString, out version))
{
return version;
}
}
else
{
var query = System.Web.HttpUtility.ParseQueryString(request.RequestUri.Query);
string versionStr = query[VersionHeaderName];
int version = 0;
int.TryParse(versionStr, out version);
if (version > 0)
return version;
}
return null;
}
}
路线工厂:
/// <summary>
/// Versioning support for the WebAPI controllers
/// </summary>
public class RouteVersionAttribute : RouteFactoryAttribute
{
public int Version { get; private set; }
public RouteVersionAttribute() : this(null, 1)
{
}
/// <summary>
/// Specify a version for the WebAPI controller
/// </summary>
/// <param name="version"></param>
public RouteVersionAttribute(int version) : this(null, version)
{
}
public RouteVersionAttribute(string template, int version)
: base(template)
{
Version = version;
}
public override IDictionary<string, object> Constraints
{
get
{
var constraints = new HttpRouteValueDictionary();
constraints.Add("version", new RouteVersionHttpConstraint(Version));
return constraints;
}
}
public override IDictionary<string, object> Defaults
{
get
{
var defaults = new HttpRouteValueDictionary();
defaults.Add("version", 1);
return defaults;
}
}
}
用法:
[RouteVersion("api/versiontest", 1)]
public class Version1TestController : BaseApiController
{
// get: api/versiontest
[HttpGet]
public HttpResponseMessage get()
{
return Request.CreateResponse(HttpStatusCode.OK, new { Version = "API Version 1 selected" });
}
}
[RouteVersion("api/versiontest", 2)]
public class Version2TestController : ApiController
{
// get: api/versiontest
[HttpGet]
public HttpResponseMessage get()
{
return Request.CreateResponse(HttpStatusCode.OK, new { Version = "API Version 2 selected" });
}
}
答案 2 :(得分:0)
我扩展了Michael Brown's answer以允许设置默认版本:
仅现在我在考虑如何使其与Swashbuckle招摇工具一起使用。
RouteVersionAttribute:
using System.Collections.Generic;
using System.Web.Http.Routing;
namespace YourNameSpace.Filters
{
/// <summary>
/// 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'.
/// <para>https://stackoverflow.com/a/28934352/3187389</para>
/// <para>https://stackoverflow.com/questions/25299889/customize-maphttpattributeroutes-for-web-api-versioning</para>
/// </summary>
public class RouteVersionAttribute : RouteFactoryAttribute
{
public int Version { get; private set; }
public int VersionDefault { get; private set; }
public RouteVersionAttribute() : this(null, 1, true)
{
}
/// <summary>
/// Specify a version for the WebAPI controller or an action method
/// for example: [RouteVersion("Test", 1)] or [RouteVersion("Test", 1, true)]
/// </summary>
/// <param name="version"></param>
/// <param name="isDefault"></param>
public RouteVersionAttribute(int version, bool isDefault = false) : this(null, version, isDefault)
{
}
/// <summary>
/// Specify a version for the WebAPI controller or an action method
/// for example: [RouteVersion("Test", 1)] or [RouteVersion("Test", 1, true)]
/// </summary>
/// <param name="template"></param>
/// <param name="version"></param>
/// <param name="isDefault"></param>
public RouteVersionAttribute(string template, int version, bool isDefault = false)
: base(template)
{
Version = version;
if (isDefault)
VersionDefault = version;
}
public override IDictionary<string, object> Constraints
{
get
{
var constraints = new HttpRouteValueDictionary();
constraints.Add("version", new RouteVersionHttpConstraint(Version, VersionDefault));
return constraints;
}
}
public override IDictionary<string, object> 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
{
/// <summary>
/// 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'.
/// <para>https://stackoverflow.com/a/28934352/3187389</para>
/// <para>https://stackoverflow.com/questions/25299889/customize-maphttpattributeroutes-for-web-api-versioning</para>
/// </summary>
public class RouteVersionHttpConstraint : IHttpRouteConstraint
{
public const string VersionHeaderName = "api-version";
private readonly int VersionDefault = 1;
/// <summary>
/// Add a route constraint to detect version header or by query string
/// </summary>
/// <param name="allowedVersion"></param>
public RouteVersionHttpConstraint(int allowedVersion, int versionDefault)
{
AllowedVersion = allowedVersion;
VersionDefault = versionDefault;
}
public int AllowedVersion
{
get;
private set;
}
/// <summary>
/// Perform the controller match
/// </summary>
/// <param name="request"></param>
/// <param name="route"></param>
/// <param name="parameterName"></param>
/// <param name="values"></param>
/// <param name="routeDirection"></param>
/// <returns></returns>
public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
{
if (routeDirection == HttpRouteDirection.UriResolution)
{
int version = GetVersionHeaderOrQuery(request) ?? VersionDefault;
if (version == AllowedVersion)
{
return true;
}
}
return false;
}
/// <summary>
/// Check the request header, and the query string to determine if a version number has been provided
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private int? GetVersionHeaderOrQuery(HttpRequestMessage request)
{
string versionAsString;
if (request.Headers.TryGetValues(VersionHeaderName, out IEnumerable<string> 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;
}
}
}
用法(可以在控制器或操作方法上使用):
#region Temporary Tests
// {{BaseUrl}}Test?api-version=1
[HttpGet]
[RouteVersion("Test", 1)]
public async Task<IHttpActionResult> 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<IHttpActionResult> Test4([FromBody]GetCustomerW2GsForPropertyRequest request)
{
return await Task.FromResult(Ok("API Version 2, 3 or 4 selected"));
}
[HttpGet]
[RouteVersion("Test", 5, true)]
public async Task<IHttpActionResult> Test5([FromBody]GetCustomerW2GsForPropertyRequest request)
{
return await Task.FromResult(Ok("API Version 5 selected"));
}
#endregion Temporary Tests