ASP.Net Core中关于WebApi几种版本控制对比详解

1. 概述

ASP.Net Core中的WebApi版本控制是一种比较重要的技术,在大型项目中被广泛应用。在现有应用程序中进行版本控制,可以确保应用程序的兼容性、可维护性和方便升级,同时也可以保证系统的可靠性和稳定性。

2. 无版本控制

在WebApi没有版本控制的情况下,所有的操作都被视为同一个版本。如果需要修改API的某些内容,那么就必须修改代码,并对整个应用程序重新编译和部署,这可能会导致应用程序停机和不必要的浪费。

public class ValuesController : ApiController

{

// GET api/values

public IEnumerable Get()

{

return new string[] { "value1", "value2" };

}

// GET api/values/5

public string Get(int id)

{

return "value";

}

// POST api/values

public void Post([FromBody]string value)

{

}

// PUT api/values/5

public void Put(int id, [FromBody]string value)

{

}

// DELETE api/values/5

public void Delete(int id)

{

}

}

2.1 无版本控制的缺陷

按照上述代码,如果后续需要对其中某个方法进行修改,例如需要增加一个GetByName方法,但是又不能修改现有的代码时,我们就必须重新创建一个新的控制器。

这种方式会导致较为严重的问题。例如,除了增加GetByName方法,我们还需要增加GetById方法,但是这两个方法的返回结果不同,就会出现冲突。此时就需要创建两个不同的控制器。

3. URL路由版本控制

在URL路由版本控制中,我们可以将API的版本信息放在URL中,例如:http://localhost:5000/api/v1/values、http://localhost:5000/api/v2/values等,这样就能方便地控制不同版本之间的区别。

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc(options =>

{

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status400BadRequest));

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status406NotAcceptable));

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status500InternalServerError));

options.Filters.Add(new ProducesDefaultResponseTypeAttribute());

options.CacheProfiles.Add("Never", new CacheProfile() { NoStore = true, Duration = 0 });

}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)

{

app.UseMvc(routes =>

{

routes.MapRoute(

name: "default",

template: "api/v{version:apiVersion}/[controller]");

});

}

3.1 URL路由版本控制的优缺点

URL路由版本控制的主要优点是易于理解和实现,所有的版本信息都可以简单地通过URL进行管理。此外,不同的版本可以准确地映射到不同的功能实现,使得系统更具灵活性。

其缺点在于,URL路由版本控制经常需要修改路由配置,这可能会导致路由映射出现故障。同时,如果API的版本不断增加,URL的长度会变得越来越长,这可能会影响API在网络传输中的性能。

4. 自定义头版本控制

另一种版本控制方式是采用自定义头版本控制。我们可以在HTTP头中添加自定义的版本信息,例如:X-API-Version:1.0、X-API-Version:2.0等,这样就能方便地控制不同版本之间的区别。

public class ApiVersionReader : IApiVersionReader

{

public static readonly string VersionHeaderName = "X-API-Version";

public const string DefaultVersion = "1.0";

public string Read(HttpRequest request)

{

return request.Headers.ContainsKey(VersionHeaderName) ?

request.Headers[VersionHeaderName].ToString() : DefaultVersion;

}

}

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc(options =>

{

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status400BadRequest));

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status406NotAcceptable));

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status500InternalServerError));

options.Filters.Add(new ProducesDefaultResponseTypeAttribute());

options.CacheProfiles.Add("Never", new CacheProfile() { NoStore = true, Duration = 0 });

options.Conventions.Add(new RoutePrefixVersion());

}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

services.AddApiVersioning(options =>

{

options.ReportApiVersions = true;

options.AssumeDefaultVersionWhenUnspecified = true;

options.DefaultApiVersion = new ApiVersion(new DateTime(2020, 01, 01));

options.ApiVersionReader = new ApiVersionReader();

});

}

[ApiVersion("1.0")]

[RoutePrefix("api/values")]

public class ValuesController : ApiController

{

// GET api/values

public IEnumerable Get()

{

return new string[] { "value1", "value2" };

}

// GET api/values/5

public string Get(int id)

{

return "value";

}

// POST api/values

[HttpPost]

public void Post([FromBody]string value)

{

}

// PUT api/values/5

[HttpPut]

public void Put(int id, [FromBody]string value)

{

}

// DELETE api/values/5

[HttpDelete]

public void Delete(int id)

{

}

}

4.1 自定义头版本控制的优缺点

自定义头版本控制的优点是能够准确地控制不同版本之间的区别,同时也能够方便地修改版本信息,不会给URL带来冗余的信息。另外,自定义头版本控制还支持多个版本并存。

其缺点是在实际应用中可能会出现一些意外的问题,在使用中需要特别注意,否则可能会出现许多不必要的麻烦。

5. Accept版本控制

在Accept版本控制中,我们可以将版本信息放在HTTP请求头Accept中,例如:Accept:application/vnd.example.v1+json、Accept:application/vnd.example.v2+json等。

public class JsonPatchDocumentFormatter : JsonInputFormatter

{

public JsonPatchDocumentFormatter() : base(new JsonSerializerSettings(),

ArrayPool.Shared)

{

SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/json-patch+json"));

}

public override bool CanRead(InputFormatterContext context)

{

var request = context.HttpContext.Request;

if (!HasJsonContentType(request.ContentType))

{

return false;

}

if (request.Method.Equals("PATCH") && request.Headers.ContainsKey("Accept"))

{

var acceptHeaders = request.Headers["Accept"].ToString().Split(new[] { ',' },

StringSplitOptions.RemoveEmptyEntries);

var jsonPatchContentType =

new MediaTypeHeaderValue("application/json-patch+json");

return acceptHeaders.Any(header =>

MediaTypeHeaderValue.Parse(header)

.IsSubsetOf(jsonPatchContentType));

}

return base.CanRead(context);

}

}

public void ConfigureServices(IServiceCollection services)

{

services.AddMvc(options =>

{

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status400BadRequest));

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status406NotAcceptable));

options.Filters.Add(new ProducesResponseTypeAttribute(StatusCodes.Status500InternalServerError));

options.Filters.Add(new ProducesDefaultResponseTypeAttribute());

options.CacheProfiles.Add("Never", new CacheProfile() { NoStore = true, Duration = 0 });

options.Conventions.Add(new RoutePrefixVersion());

options.InputFormatters.Insert(0, new JsonPatchDocumentFormatter());

}).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

services.AddApiVersioning(options =>

{

options.ReportApiVersions = true;

options.AssumeDefaultVersionWhenUnspecified = true;

options.DefaultApiVersion = new ApiVersion(new DateTime(2020, 01, 01));

options.ApiVersionReader = new MediaTypeApiVersionReader();

});

}

[ApiVersion("1.0")]

[RoutePrefix("api/values")]

public class ValuesController : ApiController

{

// GET api/values

public IEnumerable Get()

{

return new string[] { "value1", "value2" };

}

// GET api/values/5

public string Get(int id)

{

return "value";

}

// POST api/values

[HttpPost]

public void Post([FromBody]string value)

{

}

// PUT api/values/5

[HttpPut]

public void Put(int id, [FromBody]string value)

{

}

// DELETE api/values/5

[HttpDelete]

public void Delete(int id)

{

}

// PATCH api/values/5

[HttpPatch("{id}")]

public IActionResult Patch(int id, [FromBody] JsonPatchDocument value)

{

return new NoContentResult();

}

}

5.1 Accept版本控制的优缺点

Accept版本控制的优点是支持HTTP1.1标准,客户端和服务器都可以自由控制版本信息,非常灵活。同时,该方法可以在一定程度上范围不同类型的版本控制。

其缺点是与其他版本控制方法相比,语义化不够明确,传输成本和网络延迟较高。

6. 结论

不同的版本控制方式各有优缺点,在实际应用中应根据情况选择合适的版本控制方式。URL路由版本控制和自定义头版本控制比较灵活,但易于出现问题。Accept版本控制采用了HTTP1.1标准,并且支持不同的类型,但是传输成本和网络延迟较高。因此,在具体实现时需要综合考虑各种因素,才能选择一个最合适的版本控制方式。

后端开发标签