我遇到 API 版本控制 (ASP.NET Core API 2.1) 问题。我试图取代现有 Controller 的一种方法,而不必复制以前版本中的所有方法。我认为这会起作用,但它给我带来了路由冲突的问题。示例:
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" });
}
}
然后我得到错误:
The method 'Get' on path 'xxx' is registered multiple times.
我想在两个版本中都支持方法 GetValue(string value),但我不想在每次版本更新时都在新 Controller 中复制代码。我只想取代一种方法。这是可能的,还是我必须复制整个以前的 Controller 和其中的每个方法?这有效,但感觉很糟糕:
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 });
}
}
这现在可以工作了,但我只是无缘无故地复制了代码。在具有大量代码的 Controller 中,这感觉就像代码的味道。有解决方法吗?
最佳答案
继承是一件棘手的事情,可以说与没有继承概念的 REST 或 Web API 不一致。然而,斗争是真实的,您真正想要实现的是保持实现 DRY - 完全公平。在您的示例中,您没有尝试继承 API 版本,但我已经看到很多次了。我不推荐它。
要了解为什么第一次尝试失败而第二次尝试成功,您需要考虑 URL(例如 标识符)。问题不在于 REST,而在于像 ASP.NET Core 这样的框架如何将 HTTP 请求映射到代码。失败是有道理的,因为 2 个不同的实现 报告它们处理相同的请求。从调度的角度来看,这是模棱两可的。
在我看来, Controller (和一般的 Web 服务)不应该包含业务逻辑。该 API 充当 HTTP facade,通过网络表示您的业务逻辑。 Controller 中的任何逻辑都应限制在 HTTP 上下文中。这可以通过提供通用功能(但不是 API)的扩展方法或基类进一步概括。如果您的业务逻辑被抽象到一个 - 由于缺乏更好的术语 - 业务层,那么重复是最小的。每个新版本都是旧版本的复制和粘贴,只有与 API 本身相关的最小更改。我已经看到创建新版本的复制/粘贴/替换方法很有效。
您的另一个选择是在 Controller 中交错 版本。这可能很讨厌,但如果版本之间的变化很小,这是一个可行的折衷方案。根据您对更改旧代码的关注程度,如果您发现事情变得难以为继,您总是可以稍后将事情分开。一些服务作者坚持不更改旧版本的代码,这是可以理解的,但我觉得良好的测试覆盖率减轻了这种影响。使用 Client-Driven Contracts 是另一种很好的验证方法。
要考虑的最后一件事,也是我认为在很大程度上被忽视的一件事,是您的版本控制策略。即使没有正式定义,大多数服务作者的脑海中也会有一个 N-1 或 N-2 的策略。如果您有明确定义的政策,那么这将是创建新版本时有多少行李重复代码的指标。例如,如果您的策略是 N-2,那么某些 Controller 级别的位被复制多达 3 次真的很糟糕吗?虽然理想情况下我们不希望出现重复,但在我们概括了所有其他可能的内容之后尝试重构这最后一点可能不值得付出努力。
我希望您觉得这很有见地。我很乐意进一步阐述。
关于c# - ASP.NET Core API 版本控制 - 部分取代以前的方法版本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60889396/