Asp.net WebForm Web API版本处理使用命名空间和持久化旧API

本文关键字:API 命名空间 持久化 处理 net WebForm Web 版本 Asp | 更新日期: 2023-09-27 18:14:34

我有一个包含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)

我读了很多帖子,但没有一个适合我!我还创建了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;
    }
}

Asp.net WebForm Web API版本处理使用命名空间和持久化旧API

仅仅添加NamespaceHttpControllerSelector是不够的,你必须为它提供数据。

这条线

var namespaces = routeData.Route.DataTokens["Namespaces"] as IEnumerable<string>;

告诉它期望一个名称空间列表来查找名为"namespaces"的数据令牌中的控制器。但问题是,在WebAPI中没有简单的方法来设置数据令牌(它们最初来自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属性路由。

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
{
}

我也没有WebApiConfig文件,我应该在哪里创建它?

在同一个地方,你有那个方法调用:

RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}"
);

因此,在这里必须添加以下方法调用以启用属性路由:

GlobalConfiguration.Configure(config => 
{
    config.MapHttpAttributeRoutes();
    RouteTable.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}"
    );
});