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标准,并且支持不同的类型,但是传输成本和网络延迟较高。因此,在具体实现时需要综合考虑各种因素,才能选择一个最合适的版本控制方式。