mir*_*nd4 5 asp.net-mvc asp.net-web-api .net-core api-versioning
经过多次尝试和阅读文章后,我决定将我的问题放在这里。我想要的是:我正在研究应用程序的 api 版本控制。.NET Core(包)支持的版本格式Microsoft.AspNetCore.Mvc.Versioning是Major.Minor,这就是我想在我从事的项目中使用的版本格式。我想要的是一个后备版本,以防客户端未指定次要版本。我正在使用 .NET core 2.2,并使用api-version标头中指定的内容。相应的 API 版本控制配置如下所示:
services.AddApiVersioning(options => {
options.ReportApiVersions = true;
options.ApiVersionReader = new HeaderApiVersionReader("api-version");
options.ErrorResponses = new ApiVersioningErrorResponseProvider();
});
Run Code Online (Sandbox Code Playgroud)
我每个版本都有以下两个控制器:(为了解决这个问题,控制器被简化了):
[ApiVersion("1.0")]
[Route("api/[controller]")]
public class ValueControllerV10 : Controller
{
[HttpGet(Name = "collect")]
public String Collect()
{
return "Version 1.0";
}
}
[ApiVersion("1.1")]
[Route("api/[controller]")]
public class ValueControllerV11 : Controller
{
[HttpGet(Name = "collect")]
public String Collect()
{
return "Version 1.1";
}
}
Run Code Online (Sandbox Code Playgroud)
如果客户端指定,api-version=1.0则使用 ValueControllerV10。当然,如果客户端指定api-version=1.1,则按预期使用 ValueControllerV11。
现在我的问题来了。如果客户端指定api-version=1(因此只有主要版本而没有次要版本),则使用 ValueControllerV10。如果我没记错的话,这是因为ApiVersion.Parse("1")等于。ApiVersion.Parse("1.0")但在这种情况下我想要的是调用给定主要版本的最新版本,在我的示例中为 1.1。
我的尝试:
第一:指定[ApiVersion("1")]于ValueControllerV11
[ApiVersion("1")]
[ApiVersion("1.1")]
[Route("api/[controller]")]
public class ValueControllerV11 : Controller
{
[HttpGet(Name = "collect")]
public String Collect()
{
return "Version 1.1";
}
}
Run Code Online (Sandbox Code Playgroud)
它不起作用,它导致
AmbiguousMatchException: The request matched multiple endpoints
Run Code Online (Sandbox Code Playgroud)
为了解决这个问题,我想出了第二种方法:
第二:使用自定义IActionConstraint。为此,我遵循了这些文章:
然后我创建了以下类:
[AttributeUsage(AttributeTargets.Method)]
public class HttpRequestPriority : Attribute, IActionConstraint
{
public int Order
{
get
{
return 0;
}
}
public bool Accept(ActionConstraintContext context)
{
var requestedApiVersion = context.RouteContext.HttpContext.GetRequestedApiVersion();
if (requestedApiVersion.MajorVersion.Equals(1) && !requestedApiVersion.MinorVersion.HasValue)
{
return true;
}
return false;
}
}
Run Code Online (Sandbox Code Playgroud)
并用于ValueControllerV11:
[ApiVersion("1")]
[ApiVersion("1.1")]
[Route("api/[controller]")]
public class ValueControllerV11 : Controller
{
[HttpGet(Name = "collect")]
[HttpRequestPriority]
public String Collect()
{
return "Version 1.1";
}
}
Run Code Online (Sandbox Code Playgroud)
好吧,它解决了AmbiguousMatchException,但覆盖了包的默认行为Microsoft.AspNetCore.Mvc.Versioning,因此如果客户端使用api-version 1.1,那么她会得到 404 Not Found 返回,根据实现,这是可以理解的HttpRequestPriority
第三:有条件地使用MapSpaFallbackRoutein Startup.cs:
app.MapWhen(x => x.GetRequestedApiVersion().Equals("1") && x.GetRequestedApiVersion().MinorVersion == null, builder =>
{
builder.UseMvc(routes =>
{
routes.MapSpaFallbackRoute(
name: "spa-fallback",
defaults: new {controller = nameof(ValueControllerV11), action = "Collect"});
});
});
app.UseMvc();
Run Code Online (Sandbox Code Playgroud)
也不起作用,没有任何影响。这个名字MapSpaFallbackRoute也给我一种感觉,这不是我需要使用的......
所以我的问题是:当 中未指定次要版本时,如何引入后备“使用最新”行为api-version?提前致谢!
这本质上不支持开箱即用。浮动版本、范围等与 API 版本控制的原则相悖。API 版本并不也不可能暗示任何向后兼容性。除非您在封闭系统中控制双方,否则假设客户可以处理任何合同变更,即使您只添加一个新成员,也是一个谬论。最终,如果客户端请求 1/1.0,那么这就是他们应该得到的,否则服务器应该说它不受支持。
抛开我的观点不谈,有些人仍然想要这种行为。这不是特别简单,但您应该能够使用自定义IApiVersionRoutePolicy或自定义端点匹配器来实现您的目标 - 这取决于您使用的路由样式。
如果您仍然使用旧版路由,这可能是最简单的,因为您只需创建一个新策略或通过覆盖OnSingleMatch来扩展现有的DefaultApiVersionRoutePolicy并将其注册到您的服务配置中。您会知道这就是您正在寻找的场景,因为传入的 API 版本不会有次要版本。你是对的,并且等同于相同的,但次要版本没有合并;因此,将会出现这种情况。11.0ApiVersion.MinorVersionnull
如果您使用Endpoint Routing,则需要替换ApiVersionMatcherPolicy。以下内容应该接近您想要实现的目标:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.Versioning;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static Microsoft.AspNetCore.Mvc.Versioning.ApiVersionMapping;
public sealed class MinorApiVersionMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy
{
public MinorApiVersionMatcherPolicy(
IOptions<ApiVersioningOptions> options,
IReportApiVersions reportApiVersions,
ILoggerFactory loggerFactory )
{
DefaultMatcherPolicy = new ApiVersionMatcherPolicy(
options,
reportApiVersions,
loggerFactory );
Order = DefaultMatcherPolicy.Order;
}
private ApiVersionMatcherPolicy DefaultMatcherPolicy { get; }
public override int Order { get; }
public bool AppliesToEndpoints( IReadOnlyList<Endpoint> endpoints ) =>
DefaultMatcherPolicy.AppliesToEndpoints( endpoints );
public async Task ApplyAsync(
HttpContext httpContext,
EndpointSelectorContext context,
CandidateSet candidates )
{
var requestedApiVersion = httpContext.GetRequestedApiVersion();
var highestApiVersion = default( ApiVersion );
var explicitIndex = -1;
var implicitIndex = -1;
// evaluate the default policy
await DefaultMatcherPolicy.ApplyAsync( httpContext, context, candidates );
if ( requestedApiVersion.MinorVersion.HasValue )
{
// we're done because a minor version was specified
return;
}
var majorVersion = requestedApiVersion.MajorVersion;
for ( var i = 0; i < candidates.Count; i++ )
{
// make all candidates invalid by default
candidates.SetValidity( i, false );
var candidate = candidates[i];
var action = candidate.Endpoint.Metadata?.GetMetadata<ActionDescriptor>();
if ( action == null )
{
continue;
}
var model = action.GetApiVersionModel( Explicit | Implicit );
var maxApiVersion = model.DeclaredApiVersions
.Where( v => v.MajorVersion == majorVersion )
.Max();
// remember the candidate with the next highest api version
if ( highestApiVersion == null || maxApiVersion >= highestApiVersion )
{
highestApiVersion = maxApiVersion;
switch ( action.MappingTo( maxApiVersion ) )
{
case Explicit:
explicitIndex = i;
break;
case Implicit:
implicitIndex = i;
break;
}
}
}
if ( explicitIndex < 0 && ( explicitIndex = implicitIndex ) < 0 )
{
return;
}
var feature = httpContext.Features.Get<IApiVersioningFeature>();
// if there's a match:
//
// 1. make the candidate valid
// 2. clear any existing endpoint (ex: 400 response)
// 3. set the requested api version to the resolved value
candidates.SetValidity( explicitIndex, true );
context.Endpoint = null;
feature.RequestedApiVersion = highestApiVersion;
}
}
Run Code Online (Sandbox Code Playgroud)
然后您需要像这样更新服务配置:
// IMPORTANT: must be configured after AddApiVersioning
services.Remove( services.Single( s => s.ImplementationType == typeof( ApiVersionMatcherPolicy ) ) );
services.TryAddEnumerable( ServiceDescriptor.Singleton<MatcherPolicy, MinorApiVersionMatcherPolicy>() );
Run Code Online (Sandbox Code Playgroud)
如果我们考虑这样的控制器:
[ApiController]
[ApiVersion( "2.0" )]
[ApiVersion( "2.1" )]
[ApiVersion( "2.2" )]
[Route( "api/values" )]
public class Values2Controller : ControllerBase
{
[HttpGet]
public string Get( ApiVersion apiVersion ) =>
$"Controller = {GetType().Name}\nVersion = {apiVersion}";
[HttpGet]
[MapToApiVersion( "2.1" )]
public string Get2_1( ApiVersion apiVersion ) =>
$"Controller = {GetType().Name}\nVersion = {apiVersion}";
[HttpGet]
[MapToApiVersion( "2.2" )]
public string Get2_2( ApiVersion apiVersion ) =>
$"Controller = {GetType().Name}\nVersion = {apiVersion}";
}
Run Code Online (Sandbox Code Playgroud)
当你提出要求时api/values?api-version=2,你就会匹配2.2。
我要重申,这通常不是一个好主意,因为客户应该能够依赖稳定的版本。如果您需要预发布API(例如: ),则使用版本中的状态可能更合适。2.0-beta1
我希望这有帮助。
| 归档时间: |
|
| 查看次数: |
5642 次 |
| 最近记录: |