简体   繁体   中英

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

I want to have the raw query string as a controller action parameter:

/// <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));

I followed the same logic as for the BearerTokenFromHeaderAttribute : https://stackoverflow.com/a/59347840/4636721

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;
    }
}

But when the controller action is called with something like:

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

I get the error below:

{
   "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."
}

Not sure to really understand why, any thought?

Also to avoid any misunderstanding if I am doing this:

/// <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));

The same request works just fine...

My point is I just want to have the this.Request.QueryString.Value as a controller action parameter.

Try double-asterisk (**) catch-all parameter routes .

Something like this should do a trick you want:

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

Alright so the issues was dead simple:

Basically, I forgot to register the related 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();

    // ...
}

Which I already did for the BearerTokenFromHeaderAttribute ... but forgot to do for the RawQueryStringAttribute

Now everything works like a charm.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM