ASP.NET API版本控制

7

我对ASP.NET还很陌生,但我想为即将启动的新API实现一些版本控制。

我甚至不确定我要找的东西是否可行,但我想使用一个头部变量来实现非常干净的版本方法。

理想情况下,我想在代码结构中有一个versions文件夹,并且该文件夹中包含不同API版本的不同文件夹。 每个版本文件夹都将包含核心API代码的完整副本,因此我知道永远不会出现任何冲突等问题。我知道这会增加代码量,但为了保持代码非常干净,只会有2-3个版本的API处于活动状态。

我在互联网上找到了许多头部示例,但它们都需要将类放置在不同的命名空间中,如果我要完全复制代码,则重命名所有类是不切实际的。

我尝试做的事情可能吗?或者在处理多个类时是否有更清晰的解决方案?


你正在使用ASP.Net Web API 2吗? - Win
嗨,是的,我正在使用Web API 2。 - widget
2个回答

10

RESTful的版本化有四种基本方法:

  1. URI路径方式,形式如下:

    http://api/v2/Tasks/{TaskId}

  2. URI参数方式,形式如下:

    http://api/Tasks/{TaskId}?v=2

  3. 内容协商方式,通过HTTP头进行,例如:

    Content Type: application/vnd.taskManagerApp.v2.param.json

  4. 请求头方式,也是通过HTTP头进行,例如:

    x-taskManagerApp-version: 2

我个人比较喜欢第一种方式。你可以阅读 Mike Wasson的ASP.NET Web API: Using Namespaces to Version Web APIs

许多人对Mike Wasson的原始代码进行了修改,我喜欢Jamie Kurtz和Brian Wortman所著的ASP.NET Web API 2书中使用的那个版本

由于它涉及到太多的组件,我在GitHub上创建了一个示例项目

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 }
);

然后,您需要添加 ApiVersionConstraint

public class ApiVersionConstraint : IHttpRouteConstraint
{
    public ApiVersionConstraint(string allowedVersion)
    {
        AllowedVersion = allowedVersion.ToLowerInvariant();
    }

    public string AllowedVersion { get; private set; }

    public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName,
        IDictionary<string, object> values, HttpRouteDirection routeDirection)
    {
        object value;
        if (values.TryGetValue(parameterName, out value) && value != null)
        {
            return AllowedVersion.Equals(value.ToString().ToLowerInvariant());
        }
        return false;
    }
}

使用方法

只需将RoutePrefix放在控制器上即可完成。

[RoutePrefix("api/{apiVersion:apiVersionConstraint(v1)}/values")]
public class ValuesController : ApiController
{
    // GET api/v1/values
    [Route("")]
    public IEnumerable<string> Get()
    {
        return new string[] { "v1-value1", "v1-value2" };
    }

    // GET api/v1/values/5
    [Route("{id}")]
    public string Get(int id)
    {
        return "v1-value-" + id;
    }
}

我已经查看了您的Github示例,它对于URL版本控制非常有效,但我需要能够通过自定义标头传递版本。我尝试了不同的方法,并可以从标头中获取版本,但在从URL中删除版本后,这行代码[var controllerName = GetControllerName(routeData);]会导致错误。 - widget
抱歉,我没有用HTTP头进行版本控制的源代码。 - Win
我认为我的看法有误,并且我不确定使用标题或URL版本控制是否可以实现我想要做的事情。使用您提供的两个ValuesController.cs文件的示例,它们都具有不同的命名空间和路由前缀,因此每次我创建新版本并复制现有控制器时,我都必须更改所有命名空间和前缀...感谢您的帮助Win,我会重新开始思考。 - widget
很好的回答。我看到很多建议为每个版本创建文件夹,并在其中放置控制器,但是没有看到任何关于如何处理小版本(1.1)的评论。您对此有什么建议吗? - lcj

3
稍晚的回答,但对于仍想将版本控制应用于ASP.NET Web API堆栈的人,ASP.NET API Versioning 已成为实现此目的的一种非常普遍的方法。通过基本示例,您可以了解所有必要的步骤。
API版本控制支持查询字符串、标头、媒体类型和URL段。如果您支持多个,则可以将它们合并在一起,甚至可以创建自己的方法来提取API版本。默认的版本控制方法是通过查询字符串。尽管它很受欢迎,并且有@Win的建议,但我不建议使用URL段进行版本控制。它是最不符合RESTful的方法,因为它违反了统一接口约束,并且具有大量问题的边缘情况。
首先添加ASP.NET Web API Versioning NuGet软件包。确切地说如何进行版本控制以及如何组织代码高度主观,但这里是一个非常简单的设置:
namespace Example
{
   public class WebApiConfig
   {
       public static Configure(HttpConfiguration config)
       {
           // TODO: this is the minimum, but there are many options that can be configured
           config.AddApiVersioning();

           // TODO: remaining configuration
       }
   }

   // this is one of many possible ways you might organize controllers
   namespace Controllers
   {
       namespace V1
       {
           [ApiVersion("1.0")]
           [RoutePrefix("values")]
           public class ValuesController : ApiController
           {
               // GET /values?api-version=1.0
               [Route]
               public IHttpActionResult Get(ApiVersion version) =>
                   Ok(new []{$"v{version}-Value1", $"v{version}-Value2"});

               // GET /values/{id}?api-version=1.0
               [Route("{id}")]
               public IHttpActionResult Get(int id, ApiVersion version) =>
                   Ok($"v{version}-{id}")
           }
       }

       namespace V2
       {
           [ApiVersion("2.0")]
           [RoutePrefix("values")]
           public class ValuesController : ApiController
           {
               // GET /values?api-version=2.0
               [Route]
               public IHttpActionResult Get(ApiVersion version) =>
                   Ok(new []{$"v{version}-Value1", $"v{version}-Value2"});

               // GET /values/{id}?api-version=2.0
               [Route("{id}")]
               public IHttpActionResult Get(int id, ApiVersion version) =>
                   Ok($"v{version}-{id}")
           }
       }
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接