【问题标题】:ASP.NET Core API Versioning - Partially supplant previous method versionASP.NET Core API 版本控制 - 部分取代以前的方法版本
【发布时间】:2020-07-08 09:25:27
【问题描述】:

我遇到了 API 版本控制 (ASP.NET Core API 2.1) 的问题。我试图取代现有控制器的一种方法,而不必复制以前版本中的所有方法。我认为这会起作用,但它给我带来了路由冲突的问题。示例:

namespace MyApi.Controllers
{
    [Produces("application/json")]
    [Route("api/v{version:apiVersion}")]
    public class BaseController : Controller
    {
        public string VersionNumber => GetRouteValue<string>(ControllerContext, "version");

        protected static TValue GetRouteValue<TValue>(ControllerContext context, string name)
        {
            return (TValue)Convert.ChangeType(context.RouteData.Values[name], typeof(TValue));
        }
    }
}

namespace MyApi.Controllers
{
    [ApiVersion("1.0")]
    [ApiVersion("2.0")]
    public class ValuesController : BaseController
    {
        [HttpGet("values", Name = "GetValuesV1.0")]
        public IActionResult GetValues() => Ok(new string[] { "value 1", "value 2" });

        [HttpGet("values/{value}", Name = "GetValueV1.0")]
        public IActionResult GetValue(string value) => Ok( value });
    }
}

namespace MyApi.Controllers.V2_0
{
    [ApiVersion("2.0")]
    public class ValuesController : BaseController
    {
        [HttpGet("values", Name = "GetValuesV2.0")]
        public IActionResult GetValues() => Ok(new string[] { "value 1", "value 2", "value 3" });
    }
}

然后我得到错误:

路径 'xxx' 上的方法 'Get' 被多次注册。

我想在两个版本中都支持 GetValue(string value) 方法,但我不想在每次版本时都复制新控制器中的代码。我只想替换一种方法。这是可能的,还是我必须复制整个以前的控制器和其中的每个方法?这可行,但感觉很糟糕:

namespace MyApi.Controllers
{
    [Produces("application/json")]
    [Route("api/v{version:apiVersion}")]
    public class BaseController : Controller
    {
        public string VersionNumber => GetRouteValue<string>(ControllerContext, "version");

        protected static TValue GetRouteValue<TValue>(ControllerContext context, string name)
        {
            return (TValue)Convert.ChangeType(context.RouteData.Values[name], typeof(TValue));
        }
    }
}

namespace MyApi.Controllers
{
    [ApiVersion("1.0")]
    public class ValuesController : BaseController
    {
        [HttpGet("values", Name = "GetValuesV1.0")]
        public IActionResult GetValues() => Ok(new string[] { "value 1", "value 2" });

        [HttpGet("values/{value}", Name = "GetValueV1.0")]
        public IActionResult GetValue(string value) => Ok( value });
    }
}

namespace MyApi.Controllers.V2_0
{
    [ApiVersion("2.0")]
    public class ValuesController : BaseController
    {
        [HttpGet("values", Name = "GetValuesV2.0")]
        public IActionResult GetValues() => Ok(new string[] { "value 1", "value 2", "value 3" });

        [HttpGet("values/{value}", Name = "GetValueV2.0")]
        public IActionResult GetValue(string value) => Ok( value });
    }
}

现在可以了,但我只是无缘无故地复制了代码。在有大量代码的控制器中,这感觉就像代码异味。有解决办法吗?

【问题讨论】:

    标签: c# asp.net-core aspnet-api-versioning


    【解决方案1】:

    继承是一件棘手的事情,可以说与 REST 或没有继承概念的 Web API 不一致。然而,斗争是真实的,您真正想要实现的是保持实施 DRY - 完全公平。在您的示例中,您没有尝试继承 API 版本,但我已经见过很多次了。不推荐。

    要了解为什么您的第一次尝试无效而您的第二次尝试无效,您需要考虑 URL(例如 标识符)。问题不在于 REST,而在于像 ASP.NET Core 这样的框架如何将 HTTP 请求映射到代码。失败是有道理的,因为 2 个不同的实现报告它们处理相同的请求。从调度的角度来看,这是模棱两可的。

    在我看来,控制器(以及一般的 Web 服务)中不应包含业务逻辑。 API 充当 HTTP 外观,通过网络表示您的业务逻辑。控制器中的任何逻辑都应限制在 HTTP 的上下文中。这可以通过提供通用功能(但不是 API)的扩展方法或基类进一步概括。如果您的业务逻辑被抽象到一个 - 因为没有更好的术语 - 业务层,那么重复是最小的。每个新版本都是旧版本的复制和粘贴,只需与 API 本身相关的少量更改。我已经看到创建新版本的复制/粘贴/替换方法很有效。

    另一个选项是在控制器中交错版本。这可能很糟糕,但如果版本之间的变化很小,这是一个可行的折衷方案。根据您对更改旧代码的关注程度,如果您发现事情变得站不住脚,您可以随时将事情分开。一些服务作者坚持不更改旧版本的代码,这是可以理解的,但我觉得通过良好的测试覆盖率可以缓解。使用 Client-Driven Contracts 是另一种很好的验证方法。

    要考虑的最后一件事,我认为在很大程度上被忽视的是您的版本控制政策。即使没有正式定义它,大多数服务作者也会在他们的脑海中拥有 N-1N-2 的策略。如果您有一个明确定义的策略,这将是创建新版本时有多少行李重复代码的指标。例如,如果您的策略是 N-2,那么某些控制器级别的位被重复多达 3 次真的很糟糕吗?虽然理想情况下我们不希望重复,但在我们可以概括所有其他内容之后尝试重构最后一点可能是不值得的。

    我希望你觉得这很有见地。我很乐意进一步详细说明。

    【讨论】:

      猜你喜欢
      • 2021-06-19
      • 1970-01-01
      • 2019-08-20
      • 1970-01-01
      • 2018-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多