我们希望在接受 header 中使用内容协商来实现基于版本的 API。
我们能够通过一些继承和扩展默认的 HTTP 选择器来实现 Controller 和 API 方法。
使用以下示例代码实现 Controller 继承,
public abstract class AbstractBaseController : ApiController
{
// common methods for all api
}
public abstract class AbstractStudentController : AbstractBaseController
{
// common methods for Student related API'sample
public abstract Post(Student student);
public abstract Patch(Student student);
}
public class StudentV1Controller : AbstractStudentController
{
public override Post([FromBody]Student student) // student should be instance of StudentV1 from JSON
{
// To Do: Insert V1 Student
}
public override Patch([FromBody]Student student) // student should be instance of StudentV1 from JSON
{
// To Do: Patch V1 Student
}
}
public class StudentV2Controller : AbstractStudentController
{
//
public override Post([FromBody]Student student) // student should be instance of StudentV2 from JSON
{
// To Do: Insert V2 Student
}
}
public abstract class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class StudentV1 : Student
{
}
public class StudentV2 : Student
{
public string Email { get; set; }
}
我们已经创建了上述架构,以减少版本更改的代码,例如,如果版本 1 有 10 个 API 方法并且其中一个 API 方法发生了变化,那么它应该在版本 2 代码中可用而不修改其他 9 个(它们是从版本继承而来的) 1)。
现在,我们面临的主要问题是合约版本控制,因为我们无法实例化抽象学生的实例。当有人将 JSON 发布到 API 版本 1 时,StudentV1 实例应该在方法中传递,并且在版本 2 中相同。
有没有办法实现这一目标?
提前致谢!!
最佳答案
ASP.NET API Versioning能够实现您的目标。首先,您需要添加对ASP.NET Web API API VersioningNuGet 包的引用。
然后,您将配置您的应用程序,例如:
public class WebApiConfig
{
public static void Configure(HttpConfiguration config)
{
config.AddApiVersioning(
options => options.ApiVersionReader = new MediaTypeApiVersionReader());
}
}
您的 Controller 可能类似于:namespace MyApp.Controllers
{
namespace V1
{
[ApiVersion("1.0")]
[RoutePrefix("student")]
public class StudentController : ApiController
{
[Route("{id}", Name = "GetStudent")]
public IHttpActionResult Get(int id) =>
Ok(new Student() { Id = id });
[Route]
public IHttpActionResult Post([FromBody] Student student)
{
student.Id = 42;
var location = Link("GetStudent", new { id = student.Id });
return Created(location, student);
}
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] Student student) =>
Ok(student);
}
}
namespace V2
{
[ApiVersion("2.0")]
[RoutePrefix("student")]
public class StudentController : ApiController
{
[Route("{id}", Name = "GetStudentV2")]
public IHttpActionResult Get(int id) =>
Ok(new Student() { Id = id });
[Route]
public IHttpActionResult Post([FromBody] StudentV2 student)
{
student.Id = 42;
var location = Link("GetStudentV2", new { id = student.Id });
return Created(location, student);
}
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] StudentV2 student) =>
Ok(student);
}
}
}
我强烈反对继承。这是可能的,但这是解决 IMO 问题的错误方法。 API 和 HTTP 都不支持继承。这是支持语言的实现细节,也有点阻抗不匹配。一个关键问题是您不能取消继承方法,因此也不能取消 API。如果你真的坚持继承。选择以下选项之一:
protected
成员 例如,您可能会执行以下操作:
namespace MyApp.Controllers
{
public abstract class StudentController<T> : ApiController
where T: Student
{
protected virtual IHttpActionResult Get(int id)
{
// common implementation
}
protected virtual IHttpActionResult Post([FromBody] T student)
{
// common implementation
}
protected virtual IHttpActionResult Patch(int id, [FromBody] Student student)
{
// common implementation
}
}
namespace V1
{
[ApiVersion("1.0")]
[RoutePrefix("student")]
public class StudentController : StudentController<Student>
{
[Route("{id}", Name = "GetStudentV1")]
public IHttpActionResult Get(int id) => base.Get(id);
[Route]
public IHttpActionResult Post([FromBody] Student student) =>
base.Post(student);
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] Student student) =>
base.Patch(student);
}
}
namespace V2
{
[ApiVersion("2.0")]
[RoutePrefix("student")]
public class StudentController : StudentController<StudentV2>
{
[Route("{id}", Name = "GetStudentV2")]
public IHttpActionResult Get(int id) => base.Get(id);
[Route]
public IHttpActionResult Post([FromBody] StudentV2 student) =>
base.Post(student);
[Route("{id}")]
public IHttpActionResult Patch(int id, [FromBody] StudentV2 student) =>
base.Patch(student);
}
}
}
还有其他方法,但这只是一个例子。如果您定义了一个合理的版本控制策略(例如:N-2 个版本),那么重复的数量是最少的。继承可能会导致比它解决的问题更多的问题。当您按媒体类型进行版本控制时,默认行为使用
v
媒体类型参数来指示 API 版本。如果您愿意,您可以更改名称。其他形式的媒体类型版本控制也是可能的(例如:application/json+student.v1
,您需要一个自定义的IApiVersionReader,因为没有标准格式。此外,您必须更新 ASP.NET MediaTypeFormatter 配置中的映射。内置媒体类型映射不考虑媒体类型参数(例如v
参数没有影响)。下表显示了映射:
方法
标题
例子
GET
Accept
application/json;v=1.0
PUT
Content-Type
application/json;v=1.0
POST
Content-Type
application/json;v=1.0
PATCH
Content-Type
application/json;v=1.0
DELETE
Accept
或Content-Type
application/json;v=1.0
DELETE
是一个异常情况,因为它不需要输入或输出媒体类型。 Content-Type
将始终优先于Accept
,因为它是主体所必需的。 DELETE
API 可以设为 API 版本中立,这意味着将采用任何 API 版本,包括根本没有。如果您想在不需要媒体类型的情况下允许DELETE
,这可能很有用。另一种替代方法是使用媒体类型和查询字符串版本控制方法。这将允许在DELETE
API 的查询字符串中指定 API 版本。在电线上,它看起来像:
要求
POST /student HTTP/2
Host: localhost
Content-Type: application/json;v=2.0
Content-Length: 37
{"firstName":"John","lastName":"Doe"}
回复HTTP/2 201 Created
Content-Type: application/json;v=2.0
Content-Length: 45
Location: http://localhost/student/42
{"id":42,"firstName":"John","lastName":"Doe"}
关于ASP.NET Web API 契约(Contract)版本控制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39371014/