自动给 Asp.Net Core WebApi 增加 ApiVersionNeutral

时间:2021-05-10 16:05:40

自动给 Asp.Net Core WebApi 增加 ApiVersionNeutral

Intro

新增加一个 Controller 的时候,经常忘记在 Controller 上增加 ApiVersion ,结果就导致前端使用指定的 ApiVersion 访问的时候就会失败,不支持的 Api 版本。

错误信息如下:

{
"error": {
"code": "UnsupportedApiVersion",
"message": "The HTTP resource that matches the request URI 'http://localhost:5000/api/values' does not support the API version '1.2'.",
"innerError": null
}
}

分析源代码

Asp.Net Core ApiVersion 源码地址:https://github.com/Microsoft/aspnet-api-versioning

使用 ApiVersion 会在注册服务的地方注册 ApiVersion 相关的服务

    services.AddApiVersioning();

找到源码 会发现注册服务的时候把 mvc 默认的 ActionSelector 替换成了 ApiVersionActionSelector,然后查看 ApiVersionActionSelector 的源码,找到了以下几处关键代码

ApiVersion 服务注册

自动给 Asp.Net Core WebApi 增加 ApiVersionNeutral

ApiVersionNetural

自动给 Asp.Net Core WebApi 增加 ApiVersionNeutral

ApiVersionNeutralAttribute

自动给 Asp.Net Core WebApi 增加 ApiVersionNeutral

ApiVersionActionSelector

自动给 Asp.Net Core WebApi 增加 ApiVersionNeutral

ControllerApiVentionBuilder

自动给 Asp.Net Core WebApi 增加 ApiVersionNeutral

总结如下:

如果 Controller 的 Attribute 定义的有 ApiVersionNeutralAttribute 就会忽略 ApiVersion 的限制,即使没有使用 ApiVersion 或者使用任意一个 ApiVersion 都可以路由到 Action,都可以访问得到,也不会出现开篇提到的错误。

解决方案

可以自己实现一个 IControllerModelConvention,去给没有定义 ApiVersion 的控制器加 ApiVersionNeutralAttribute,实现代码如下:

public class ApiControllerVersionConvention : IControllerModelConvention
{
public void Apply(ControllerModel controller)
{
if (!(controller.ControllerType.IsDefined(typeof(ApiVersionAttribute)) || controller.ControllerType.IsDefined(typeof(ApiVersionNeutralAttribute))))
{
if (controller.Attributes is List<object>
attributes)
{
attributes.Add(new ApiVersionNeutralAttribute());
}
}
}
}

在注册 Mvc 服务的时候,配置 MvcOptions

services.AddMvc(options =>
{
options.Conventions.Add(new ApiControllerVersionConvention());
});

启动项目,这时候再访问原来因为没有定义 ApiVersion 的控制器下的路由,这时就不会再报错了,使用任意一个 ApiVersion 也都不会有问题了,问题解决啦~~~

扩展方法

为了方便使用,你也可以加一个扩展方法,在扩展方法里配置 MvcOptions,根据自己的需要,我觉得两种方式都 OK 的,扩展方法示例如下:

public static class MvcBuilderExtensions
{
public static IMvcBuilder AddApiControllerVersion(this IMvcBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
builder.Services.Configure<MvcOptions>(options=> options.Conventions.Add(new ApiControllerVersionConvention()));
return builder;
}
}

使用的时候可以直接在 AddMvc 之后加上扩展方法就可以了

services.AddMvc()
.AddApiControllerVersion();

End

问题解决,完美收官,最后还是要说一下,注意这个的使用情景,如果你要指定一个默认的 ApiVersion 有更好的方法,直接配置 ApiVersioningOptions 中的 DefaultApiVersion

就可以了

services.AddApiVersioning(options =>
{
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = ApiVersion.Default;
});

如果你的 ApiVersion 不定,可能有些 Api 的 ApiVersion 会经常变,可以使用这种方式。

有问题欢迎联系~~