Asp.net WebForm Web API 使用命名空间处理版本并保留旧 API
Asp.net WebForm Web API version handling using namespace and persist old API
我有一个包含 Web Api v2 的 asp.net WebForm 项目。我没有考虑 api 版本控制并使用下面的简单路径添加我的整个 api:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
但是,现在,我需要创建版本控制,因为我有很多更改并且不想让使用旧 api 的老客户失望。所以我创建了一个名为 APIv2 的新文件夹,并在那里创建了我的控制器(与旧的 api 同名)。问题是我怎么能路由这样的东西:
MyWebsite/API/Item => For ItemController OF OLD API
MyWebsite/APIv2/Item => For ItemController Of New API (Version 2)
我读了很多 post 但其中 none 对我有用!我还创建了 NamespaceHttpControllerSelector,但它也不起作用。
请举例说明如何处理这个问题。
P.S:当我创建简单的路由,例如为新 api 创建旧路由时,它说找到重复的控制器! (虽然我使用不同的命名空间)
编辑(添加我的完整代码):
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration));
RouteTable.Routes.MapHttpRoute(
name: "VersionedApi",
routeTemplate: "api/v2/{controller}");
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}");
以及 NamespaceHttpControllerSelector:
public class NamespaceHttpControllerSelector : DefaultHttpControllerSelector
{
private const string ControllerKey = "controller";
private readonly HttpConfiguration _configuration;
private readonly Lazy<IEnumerable<Type>> _duplicateControllerTypes;
public NamespaceHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
_configuration = configuration;
_duplicateControllerTypes = new Lazy<IEnumerable<Type>>(GetDuplicateControllerTypes);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var routeData = request.GetRouteData();
if (routeData == null || routeData.Route == null || routeData.Route.DataTokens == null || routeData.Route.DataTokens["Namespaces"] == null)
return base.SelectController(request);
// Look up controller in route data
object controllerName;
routeData.Values.TryGetValue(ControllerKey, out controllerName);
var controllerNameAsString = controllerName as string;
if (controllerNameAsString == null)
return base.SelectController(request);
//get the currently cached default controllers - this will not contain duplicate controllers found so if
// this controller is found in the underlying cache we don't need to do anything
var map = base.GetControllerMapping();
if (map.ContainsKey(controllerNameAsString))
return base.SelectController(request);
//the cache does not contain this controller because it's most likely a duplicate,
// so we need to sort this out ourselves and we can only do that if the namespace token
// is formatted correctly.
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
if (namespaces == null)
return base.SelectController(request);
//see if this is in our cache
var found = _duplicateControllerTypes.Value
.Where(x => string.Equals(x.Name, controllerNameAsString + ControllerSuffix, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault(x => namespaces.Contains(x.Namespace));
if (found == null)
return base.SelectController(request);
return new HttpControllerDescriptor(_configuration, controllerNameAsString, found);
}
private IEnumerable<Type> GetDuplicateControllerTypes()
{
var assembliesResolver = _configuration.Services.GetAssembliesResolver();
var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
//we have all controller types, so just store the ones with duplicate class names - we don't
// want to cache too much and the underlying selector caches everything else
var duplicates = controllerTypes.GroupBy(x => x.Name)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToArray();
return duplicates;
}
}
可以使用 Web Api 属性路由。
public ItemControllerV2 : ApiController
{
[Route("v2/item/{id:int}")]
public Item Get(int id)
{
....
}
}
还记得在您的 Web Api 配置中启用属性路由
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
//rest of your Web Api configuration
}
}
您可以在此处找到有关属性路由的更多信息:http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
编辑
如果正如您在评论中所写,您在控制器中有很多方法,并且您不想向每个方法添加属性,您可以使用 RoutePrefix
属性:
[RoutePrefix("v2/item")]
public ItemControllerV2 : ApiController
{
}
Also I don't have WebApiConfig file, where should I create it ?
这是调用该方法的地方:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
因此,在这个地方您必须添加以下方法调用以启用属性路由:
GlobalConfiguration.Configure(config =>
{
config.MapHttpAttributeRoutes();
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
});
只加[=14=是不够的,还得提供数据
这一行
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
表示它需要一个名称空间列表来在名为 "Namespaces" 的数据令牌中查找控制器。但问题是,在 WebAPI 中设置 DataTokens 并不容易(它们最初来自 MVC),因此我们必须更改代码。
替换
//the cache does not contain this controller because it's most likely a duplicate,
// so we need to sort this out ourselves and we can only do that if the namespace token
// is formatted correctly.
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
if (namespaces == null)
return base.SelectController(request);
//see if this is in our cache
var found = _duplicateControllerTypes.Value
.Where(x => string.Equals(x.Name, controllerNameAsString + ControllerSuffix, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault(x => namespaces.Contains(x.Namespace));
和
var @namespace = routeData.Values["namespace"] as string;
if (@namespace == null)
return base.SelectController(request);
//see if this is in our cache
var found = _duplicateControllerTypes.Value
.Where(x => string.Equals(x.Name, controllerNameAsString + ControllerSuffix, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault(x => x.Namespace == @namespace);
并按如下方式更改路由:
RouteTable.Routes.MapHttpRoute(
name: "VersionedApi",
routeTemplate: "api/v2/{controller}",
defaults: new {@namespace = "your namespace for v2 controllers"}
);
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}",
defaults: new {@namespace = "your namespace for v1 controllers"}
);
我有一个包含 Web Api v2 的 asp.net WebForm 项目。我没有考虑 api 版本控制并使用下面的简单路径添加我的整个 api:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
但是,现在,我需要创建版本控制,因为我有很多更改并且不想让使用旧 api 的老客户失望。所以我创建了一个名为 APIv2 的新文件夹,并在那里创建了我的控制器(与旧的 api 同名)。问题是我怎么能路由这样的东西:
MyWebsite/API/Item => For ItemController OF OLD API
MyWebsite/APIv2/Item => For ItemController Of New API (Version 2)
我读了很多 post 但其中 none 对我有用!我还创建了 NamespaceHttpControllerSelector,但它也不起作用。
请举例说明如何处理这个问题。
P.S:当我创建简单的路由,例如为新 api 创建旧路由时,它说找到重复的控制器! (虽然我使用不同的命名空间)
编辑(添加我的完整代码):
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration));
RouteTable.Routes.MapHttpRoute(
name: "VersionedApi",
routeTemplate: "api/v2/{controller}");
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}");
以及 NamespaceHttpControllerSelector:
public class NamespaceHttpControllerSelector : DefaultHttpControllerSelector
{
private const string ControllerKey = "controller";
private readonly HttpConfiguration _configuration;
private readonly Lazy<IEnumerable<Type>> _duplicateControllerTypes;
public NamespaceHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
{
_configuration = configuration;
_duplicateControllerTypes = new Lazy<IEnumerable<Type>>(GetDuplicateControllerTypes);
}
public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
{
var routeData = request.GetRouteData();
if (routeData == null || routeData.Route == null || routeData.Route.DataTokens == null || routeData.Route.DataTokens["Namespaces"] == null)
return base.SelectController(request);
// Look up controller in route data
object controllerName;
routeData.Values.TryGetValue(ControllerKey, out controllerName);
var controllerNameAsString = controllerName as string;
if (controllerNameAsString == null)
return base.SelectController(request);
//get the currently cached default controllers - this will not contain duplicate controllers found so if
// this controller is found in the underlying cache we don't need to do anything
var map = base.GetControllerMapping();
if (map.ContainsKey(controllerNameAsString))
return base.SelectController(request);
//the cache does not contain this controller because it's most likely a duplicate,
// so we need to sort this out ourselves and we can only do that if the namespace token
// is formatted correctly.
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
if (namespaces == null)
return base.SelectController(request);
//see if this is in our cache
var found = _duplicateControllerTypes.Value
.Where(x => string.Equals(x.Name, controllerNameAsString + ControllerSuffix, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault(x => namespaces.Contains(x.Namespace));
if (found == null)
return base.SelectController(request);
return new HttpControllerDescriptor(_configuration, controllerNameAsString, found);
}
private IEnumerable<Type> GetDuplicateControllerTypes()
{
var assembliesResolver = _configuration.Services.GetAssembliesResolver();
var controllersResolver = _configuration.Services.GetHttpControllerTypeResolver();
var controllerTypes = controllersResolver.GetControllerTypes(assembliesResolver);
//we have all controller types, so just store the ones with duplicate class names - we don't
// want to cache too much and the underlying selector caches everything else
var duplicates = controllerTypes.GroupBy(x => x.Name)
.Where(x => x.Count() > 1)
.SelectMany(x => x)
.ToArray();
return duplicates;
}
}
可以使用 Web Api 属性路由。
public ItemControllerV2 : ApiController
{
[Route("v2/item/{id:int}")]
public Item Get(int id)
{
....
}
}
还记得在您的 Web Api 配置中启用属性路由
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
//rest of your Web Api configuration
}
}
您可以在此处找到有关属性路由的更多信息:http://www.asp.net/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2
编辑
如果正如您在评论中所写,您在控制器中有很多方法,并且您不想向每个方法添加属性,您可以使用 RoutePrefix
属性:
[RoutePrefix("v2/item")]
public ItemControllerV2 : ApiController
{
}
Also I don't have WebApiConfig file, where should I create it ?
这是调用该方法的地方:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
因此,在这个地方您必须添加以下方法调用以启用属性路由:
GlobalConfiguration.Configure(config =>
{
config.MapHttpAttributeRoutes();
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);
});
只加[=14=是不够的,还得提供数据
这一行
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
表示它需要一个名称空间列表来在名为 "Namespaces" 的数据令牌中查找控制器。但问题是,在 WebAPI 中设置 DataTokens 并不容易(它们最初来自 MVC),因此我们必须更改代码。
替换
//the cache does not contain this controller because it's most likely a duplicate,
// so we need to sort this out ourselves and we can only do that if the namespace token
// is formatted correctly.
var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;
if (namespaces == null)
return base.SelectController(request);
//see if this is in our cache
var found = _duplicateControllerTypes.Value
.Where(x => string.Equals(x.Name, controllerNameAsString + ControllerSuffix, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault(x => namespaces.Contains(x.Namespace));
和
var @namespace = routeData.Values["namespace"] as string;
if (@namespace == null)
return base.SelectController(request);
//see if this is in our cache
var found = _duplicateControllerTypes.Value
.Where(x => string.Equals(x.Name, controllerNameAsString + ControllerSuffix, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault(x => x.Namespace == @namespace);
并按如下方式更改路由:
RouteTable.Routes.MapHttpRoute(
name: "VersionedApi",
routeTemplate: "api/v2/{controller}",
defaults: new {@namespace = "your namespace for v2 controllers"}
);
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}",
defaults: new {@namespace = "your namespace for v1 controllers"}
);