如何通过模型绑定将原始查询字符串作为控制器操作参数?

How to have the raw query string as a controller action parameter via model binding?

我想将原始查询字符串作为控制器操作参数:

/// <summary>
/// Return filtered wire transfers
/// </summary>
[HttpGet("wire-transfers")]
[ProducesResponseType(typeof(LoadResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public Task<ActionResult<ProxyLoadResult>> GetAsync(
    [BearerTokenFromHeader] string bearerToken,
    [RawQueryString] string rawQueryString,
    CancellationToken cancellationToken = default) =>
    this.TryCatchRefitCallExceptionAsync(
        () => _memberCreditorWireTransfersService
            .GetAsync(bearerToken, rawQueryString, cancellationToken));

我遵循与 BearerTokenFromHeaderAttribute 相同的逻辑:

RawQueryStringAttribute.cs:

[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public class RawQueryStringAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider
{
    public BindingSource BindingSource =>
        RawQueryStringBindingSource.Instance;

    public string? Name { get; set; }
}

RawQueryStringBindingSource.cs:

public static class RawQueryStringBindingSource
{
    public const string Id = "RawQueryString";
    public const string Name = Id;

    public static readonly BindingSource Instance = new BindingSource(
        Id,
        Name,
        isGreedy: false,
        isFromRequest: true);
}

RawQueryStringValueProvider.cs:

public class RawQueryStringValueProvider : BindingSourceValueProvider
{
    public RawQueryStringValueProvider(BindingSource bindingSource, string rawQueryString) : base(bindingSource) => 
        RawQueryString = rawQueryString;

    private string RawQueryString { get; }

    public override bool ContainsPrefix(string prefix) => 
        false;

    public override ValueProviderResult GetValue(string key) =>
        string.IsNullOrEmpty(key)
            ? new ValueProviderResult(RawQueryString)
            : ValueProviderResult.None;
}

RawQueryStringValueProviderFactory.cs:

public class RawQueryStringValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        var rawQueryString = context.ActionContext.HttpContext.Request?.QueryString.Value;

        context.ValueProviders.Add(
            new RawQueryStringValueProvider(RawQueryStringBindingSource.Instance, 
                rawQueryString!));

        return Task.CompletedTask;
    }
}

但是当控制器动作被调用时:

http://localhost:5000/api/v1/wire-transfers?requireTotalCount=true&skip=0&take=5

我收到以下错误:

{
   "errors":{
      "rawQueryString":[
         "The rawQueryString field is required."
      ]
   },
   "type":"https://tools.ietf.org/html/rfc7231#section-6.5.1",
   "title":"One or more validation errors occurred.",
   "status":400,
   "traceId":"|dd22ddb4-44ebc27fb1cbc9ba."
}

不确定真正理解为什么,有什么想法吗?

另外为了避免误会,如果我这样做的话:

/// <summary>
/// Return filtered wire transfers
/// </summary>
[HttpGet("wire-transfers")]
[ProducesResponseType(typeof(LoadResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public Task<ActionResult<ProxyLoadResult>> GetAsync(
    [BearerTokenFromHeader] string bearerToken,
    CancellationToken cancellationToken = default) =>
    this.TryCatchRefitCallExceptionAsync(
        () => _memberCreditorWireTransfersService
            .GetAsync(bearerToken, this.Request.QueryString.Value, cancellationToken));

同样的请求工作正常...

我的观点是我只想将 this.Request.QueryString.Value 作为控制器操作参数。

尝试双星号 (**) catch-all 参数 routes

像这样的东西应该可以达到你想要的效果:

[HttpGet("wire-transfers/{**rawQueryString}")]
public ... GetAsync(string rawQueryString) { ... }

好吧,问题很简单:

基本上是忘了注册相关的value provider factory(facepalm)...

public void ConfigureServices(IServiceCollection services)
{
    // ... 
    services
        .AddMvc(options =>
        {
            options.EnableEndpointRouting = false;
            options.Filters.Add<Filters.LogApiIoContentsActionFilter>();
            options.ValueProviderFactories.Add(new BearerTokenValueProviderFactory());
            options.ValueProviderFactories.Add(new RawQueryStringValueProviderFactory());
        }).AddNewtonsoftJson();

    // ...
}

我已经为 BearerTokenFromHeaderAttribute...做了,但忘记为 RawQueryStringAttribute

现在一切都很顺利。