【发布时间】:2016-10-21 09:44:19
【问题描述】:
我正在尝试制作一个版本化的 API,所以基本上我遵循了这篇文章:https://blogs.msdn.microsoft.com/webdev/2013/03/07/asp-net-web-api-using-namespaces-to-version-web-apis/
我已经为我的 WebApi 设置了一些路由,如下所示:
namespace WebApiApplication {
public static class WebApiConfig {
public static void Register(HttpConfiguration config) {
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{version}/{controller}",
defaults: new { version = "v2" }
);
config.Routes.MapHttpRoute(
name: "DefaultApiWithId",
routeTemplate: "api/{version}/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(config));
}
}}
然后我的项目有一个 Controllers 文件夹,其中包含两个文件夹“v1”和“v2”。控制器看起来像这样:
~/Controllers/v1/ProductsController.cs
namespace WebApiApplication.Controllers.v1 {
[RoutePrefix("api/v1/products")]
public class ProductsController : ApiController {
Product[] products = new Product[] {
new Product { UserReference = 1, Name = "product1" },
new Product { UserReference = 2, Name = "product2" }
};
[HttpGet]
[Route("")]
public IEnumerable<Product> GetAllProducts() {
return products;
}
[HttpGet]
[Route("{userReference}")]
public IHttpActionResult GetProducts(int userReference) {
var res = products.Where(t => t.UserReference == userReference);
if (res == null)
return NotFound();
return Ok(res);
}
}}
~/Controllers/v2/ProductsController.cs
namespace WebApiApplication.Controllers.v2 {
[RoutePrefix("api/v2/products")]
public class ProductsController : ApiController {
Product[] products = new Product[] {
new Product { UserReference = "a", Name = "product1" },
new Product { UserReference = "b", Name = "product2" }
};
[HttpGet]
[Route("")]
public IEnumerable<Product> GetAllProducts() {
return products;
}
[HttpGet]
[Route("{userReference}")]
public IHttpActionResult GetProducts(string userReference) {
var res = products.Where(t => t.UserReference == userReference);
if (res == null)
return NotFound();
return Ok(res);
}
}}
这里版本之间的唯一区别是 UserReference 在 V2 中变成了一个字符串。
因为我在两个版本中都有相同的控制器名称,所以我必须覆盖当前的“IHttpControllerSelector”,才能找到请求的控制器:
~/NamespaceHttpControllerSelector.cs
public class NamespaceHttpControllerSelector : IHttpControllerSelector {
private const string VERSION_KEY = "version";
private const string CONTROLLER_KEY = "controller";
private readonly HttpConfiguration _configuration;
private readonly Lazy<Dictionary<string, HttpControllerDescriptor>> _controllers;
private readonly HashSet<string> _duplicates;
public NamespaceHttpControllerSelector(HttpConfiguration configuration) {
_configuration = configuration;
_duplicates = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
_controllers = new Lazy<Dictionary<string, HttpControllerDescriptor>>(InitializeControllerDictionary);
}
public HttpControllerDescriptor SelectController(HttpRequestMessage request) {
IHttpRouteData routeData = request.GetRouteData();
if (routeData == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
string versionName = GetRouteVariable<string>(routeData, VERSION_KEY); // Here, I am always getting null
if (versionName == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
string controllerName = GetRouteVariable<string>(routeData, CONTROLLER_KEY);
if (controllerName == null)
throw new HttpResponseException(HttpStatusCode.NotFound);
// Find a matching controller.
string key = string.Format(CultureInfo.InvariantCulture, "{1}.{2}", versionName, controllerName);
HttpControllerDescriptor controllerDescriptor;
if (_controllers.Value.TryGetValue(key, out controllerDescriptor)) {
return controllerDescriptor;
}
else if (_duplicates.Contains(key)) {
throw new HttpResponseException(
request.CreateErrorResponse(
HttpStatusCode.InternalServerError,
"Multiple controllers were found that match this request."));
}
else {
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
public IDictionary<string, HttpControllerDescriptor> GetControllerMapping() {
return _controllers.Value;
}
private Dictionary<string, HttpControllerDescriptor> InitializeControllerDictionary() {
var dictionary = new Dictionary<string, HttpControllerDescriptor>(StringComparer.OrdinalIgnoreCase);
// Create a lookup table where key is "namespace.controller". The value of "namespace" is the last
// segment of the full namespace. For example:
// MyApplication.Controllers.V1.ProductsController => "V1.Products"
IAssembliesResolver assembliesResolver = _configuration.Services.GetAssembliesResolver();
IHttpControllerTypeResolver controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
ICollection<Type> controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
foreach (Type t in controllerTypes) {
var segments = t.Namespace.Split(Type.Delimiter);
// For the dictionary key, strip "Controller" from the end of the type name.
// This matches the behavior of DefaultHttpControllerSelector.
var controllerName = t.Name.Remove(t.Name.Length - DefaultHttpControllerSelector.ControllerSuffix.Length);
var key = string.Format(
CultureInfo.InvariantCulture,
"{0}.{1}",
segments[segments.Length - 1],
controllerName);
// Check for duplicate keys.
if (dictionary.Keys.Contains(key)) {
_duplicates.Add(key);
}
else {
dictionary[key] = new HttpControllerDescriptor(_configuration, t.Name, t);
}
}
// Remove any duplicates from the dictionary, because these create ambiguous matches.
// For example, "Foo.V1.ProductsController" and "Bar.V1.ProductsController" both map to "v1.products".
foreach (string s in _duplicates) {
dictionary.Remove(s);
}
return dictionary;
}
private static T GetRouteVariable<T>(IHttpRouteData routeData, string name) {
object result = null;
if (routeData.Values.TryGetValue(name, out result))
return (T) result;
return default(T);
}
}
问题出在“SelectController”方法中。 在执行“string versionName = GetRouteVariable(routeData, VERSION_KEY);”时,我总是在 versionName 中得到“null” (例如,当我调用“http://localhost:27039/api/v1/products/1”时)。
我应该使用 URL 中请求的版本...
我的 Global.asax.cs :
namespace WebApiApplication {
public class WebApiApplication : System.Web.HttpApplication {
protected void Application_Start() {
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}}
【问题讨论】:
-
我知道这只是一个测试,但我建议您只创建 1 个控制器并将其发布到 'api/v1' 上。然后,如果您有新版本,您可以在版本控制中创建一个新分支并将其发布到“api/v2”上。假设您需要修复第 1 版中的错误。根据您的策略,您还必须发布第 2 版,因为它们是代表同一应用的 2 个版本的 1 个应用。
-
两个版本之间的唯一区别是 Product UserReference 在 V2 中变成了字符串
标签: c# asp.net asp.net-web-api asp.net-web-api2 asp.net-web-api-routing