question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ApiExplorer handles [FromHeader] binding (on complex type) differently to [FromQuery] binding

See original GitHub issue

Describe the bug

Given the following two actions that bind query/header params to a complex type:

public IActionResult Get1([FromQuery]CustomParams queryParams)
public IActionResult Get2([FromHeader]CustomParams headerParams)
public class CustomParams
{
    public string Foo { get; set; }
    public string Bar { get; set; }
}

For the first action, ApiExplorer surfaces multiple query parameters, one for each property in the complex type. This makes sense because that’s exactly how the model binder behaves - i.e. it will look for individual query parameters and assign them to corresponding properties. However, for the second action, ApiExplorer only surfaces a single parameter corresponding to the custom type itself. It’s my understanding that the model binder behaves the same way in both cases, and so I would expect ApiExplorer to follow suit. Here’s an example of the relevant ApiExplorer data (serialized as JSON):

[
  {
    "relativePath": "Test/Get1",
    "httpMethod": "GET",
    "parameters": [
      {
        "name": "Foo",
        "source": {
          "displayName": "Query",
          "id": "Query",
          "isGreedy": false,
          "isFromRequest": true
        },
        "modelType": "System.String"
      },
      {
        "name": "Bar",
        "source": {
          "displayName": "Query",
          "id": "Query",
          "isGreedy": false,
          "isFromRequest": true
        },
        "modelType": "System.String"
      }
    ]
  },
  {
    "relativePath": "Test/Get2",
    "httpMethod": "GET",
    "parameters": [
      {
        "name": "headerParams",
        "source": {
          "displayName": "Header",
          "id": "Header",
          "isGreedy": true,
          "isFromRequest": true
        },
        "modelType": "ApiExplorerIssue.Controllers.CustomParams"
      }
    ]
  }
]

To Reproduce

> git clone git@github.com:domaindrivendev/ApiExplorerIssue.git
> cd ApiExplorerIssue
> git checkout from-header-property-bindings
> dotnet run

Then navigate to "http://localhost:5000/apidescriptions" to see the ApiExplorer data

Further technical details

  • ASP.NET Core version: 5.0

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:3
  • Comments:11 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
msftbot[bot]commented, Feb 8, 2021

Thanks for contacting us. We’re moving this issue to the Next sprint planning milestone for future evaluation / consideration. We will evaluate the request when we are planning the work for the next milestone. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

0reactions
Thepriestdudecommented, Jun 28, 2023

This becomes a whole other beast when working with Commands (CQRS) and utilizing ‘.AddEndpointFilter’ for the command, since the headers are never picked up if using a complex type:

public sealed record Command(
   [FromHeader] string HeaderKey, 
   [FromBody] string BodyKey
);

public class CommandValidator : AbstractValidator<Command>
{
    public CommandValidator()
    {
        // validation for header & body
        // ...
    }
}

[...]

public static void AddCommandEndpoints(this IEndpointRouteBuilder app)
{
   app.MapPost("/postCommand", (Command command) => {[...]})
     .AddEndpointFilter([...]) // pass command to CommandValidator
}

This will cause the validation to happen before entering the endpoint, thereby causing us never to be able to validate the header.

A workaround for this is to accept the ‘headerKey’ and ‘bodyKey’ as separate parameters, map them to the command, and then validate them. But this seems like something that is not intentional.

Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - How to use [FromHeader] attribute with custom model ...
Was able to make it work by using [FromHeader] attribute on model properties and [FromQuery] attribute on model itself to fool model binding...
Read more >
Model Binding in ASP.NET Core
[FromQuery] - Gets values from the query string. ... Special data types. There are some special data types that model binding can handle....
Read more >
Exploring the model-binding logic of minimal APIs
In this post we explore the code in CreateArgument() , and see how it fundamentally defines the model-binding behaviour of minimal APIs.
Read more >
Improvements to Model Binding in ASP.NET Core
The native model-binding layer kicks in and finds any value available around the HTTP request that can be mapped to the name of...
Read more >
Swashbuckle.AspNetCore
FormFile and if you apply the attribute it will be set to BindingSource.Form instead, which screws up ApiExplorer , the metadata component that...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found