ASP.NET Web API 契约(Contract)版本控制

标签 asp.net asp.net-mvc asp.net-web-api asp.net-web-api2 versioning

我们希望在接受 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成员
  • 将业务逻辑移出 Controller
  • 使用扩展方法或其他合作者完成共享操作

  • 例如,您可能会执行以下操作:
    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参数没有影响)。
    下表显示了映射:


    方法
    标题
    例子

    GETAcceptapplication/json;v=1.0PUTContent-Typeapplication/json;v=1.0POSTContent-Typeapplication/json;v=1.0PATCHContent-Typeapplication/json;v=1.0DELETEAcceptContent-Typeapplication/json;v=1.0
    DELETE是一个异常情况,因为它不需要输入或输出媒体类型。 Content-Type将始终优先于Accept,因为它是主体所必需的。 DELETEAPI 可以设为 API 版本中立,这意味着将采用任何 API 版本,包括根本没有。如果您想在不需要媒体类型的情况下允许DELETE,这可能很有用。另一种替代方法是使用媒体类型和查询字符串版本控制方法。这将允许在DELETEAPI 的查询字符串中指定 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/

    相关文章:

    asp.net - IIS Web 应用程序子域

    c# - 为什么 ASP.NET MVC Controller FileStreamResult 在关闭的流上出错?

    asp.net - 在 ASP.NET 中使用没有登录控件的自定义成员资格提供程序

    asp.net - 将ASP.NET成员资格表添加到我自己的现有数据库中,还是应该配置一个单独的ASP.NET成员资格数据库?

    c# - 使用 Entity Framework TPH 模式从数据库访问基类查询中扩展类的属性

    c# - 使用 http.get 以 Angular 与 asp.net Web api 下载 .xlsx 文件时出错

    c# - 单元测试 DelegatingHandler

    asp.net - Controller 、服务和工作单元——我真的应该处理数据库上下文吗?

    c# - 如何在不更改数据库模型的情况下在 Entity Framework 中添加新的实体属性

    c# - 防止多重请求 - WebAPI