ASP.NET Web API合同版本控制

Vij*_*han 5 versioning asp.net asp.net-mvc asp.net-web-api asp.net-web-api2

我们希望在accept头中使用内容协商来实现基于版本的API.

我们能够通过一些继承实现控制器和API方法,并扩展默认的HTTP选择器.

使用以下示例代码实现控制器继承,

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; }
}
Run Code Online (Sandbox Code Playgroud)

我们已经创建了上面的体系结构,以便在版本更改的情况下执行更少的代码,比如说版本1有10个API方法,并且一个API方法的更改比版本2代码中可用的更改而不修改其他9个(它们是从版本继承的) 1).

现在,我们面临的主要问题是合同版本控制,因为我们无法实例化抽象学生的实例.当有人将JSON发布到API版本1时,应该在方法中传递StudentV1的实例,并在版本2中传递相同的实例.

有没有办法实现这个目标?

提前致谢!!

Chr*_*nez 1

ASP.NET API 版本控制能够实现您的目标。首先,您需要添加对ASP.NET Web API API 版本控制NuGet 包的引用。

然后,您将配置您的应用程序,例如:

public class WebApiConfig
{
   public static void Configure(HttpConfiguration config)
   {
       config.AddApiVersioning(
          options => options.ApiVersionReader = new MediaTypeApiVersionReader());
   }
}
Run Code Online (Sandbox Code Playgroud)

您的控制器可能类似于:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

强烈建议不要继承。这是可能的,但在我看来,这是解决问题的错误方法。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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

还有其他方法,但这只是一个例子。如果您定义了合理的版本控制策略(例如: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因为它是身体所必需的。APIDELETE可以成为 API版本中立的,这意味着可以采用任何 API 版本,包括根本没有版本。如果您想允许DELETE而不需要媒体类型,这可能很有用。另一种替代方法是使用媒体类型和查询字符串版本控制方法。这将允许在 API 的查询字符串中指定 API 版本DELETE

通过网络,它看起来像:

要求

POST /student HTTP/2
Host: localhost
Content-Type: application/json;v=2.0
Content-Length: 37

{"firstName":"John","lastName":"Doe"}
Run Code Online (Sandbox Code Playgroud)

回复

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"}
Run Code Online (Sandbox Code Playgroud)