Web API model binder maps null value for action method parameter in .NET Framework 4.7

71 views Asked by At

Working with a legacy app that uses .NET Framework 4.7 and exposes a couple of Web API endpoints. The parameter is a ICollection<Guid> type.

[HttpPost("customer/process")]
public async Task<IHttpActionResult> ProcessCustomers([FromBody] ICollection<Guid> customerIds, CancellationToken cancellationToken)
{
     // customerIds is null if the collection has more than 24 Guids
}

Using the refit client to make the HTTP post request.

public interface IRefitApiClient 
{
   [HttpPost("/api/xyz/customer/process")]
   [Headers("content-type: application/json")]
   public async Task<int> ProcessCustomers([Body] IEnumerable<Guid> customerIds, CancellationToken token);
}

Body:

[
"46dadad3-f8da-4a0e-a97d-69a21684f720",
"56da249b-70b2-4e74-998f-4101541ca492",
"3afcc23e-e1f8-4b3b-9ca1-acaf038c86c4",
"7c1321b4-4944-47f7-b248-f65c6acb246e"
.
.
.
]

If the collection has 24 or less GUIDS, the endpoint receives all of them. More than 24 GUIDs, the endpoint just receives null value.

I tried increasing all the request limits in the web.config of the Web API service but no avail. The service runs on IIS but haven't made any changes there. None of these seems to help.

maxJsonLength="2147483644"
maxRequestLength="2097151"
maxAllowedContentLength="4294967295"
aspnet:MaxJsonDeserializerMembers value="10000000"

Is this is a limitation on refit client or I have to change something on IIS? However, I can successfully post a list of like 5000 guids with postman when running the service locally on IIS express in VS. This suggests something needs to be adjusted in IIS server in production. TIA

Update

I serialized the list of 200 Guids before passing them to refit, it works without any issue.

var foo = new List<Guid>{'guid1', 'guid2', 'guid3'.....};
var bar = JsonSerializer.Serialize(foo);
var res = _refitApiClient.ProcessCustomers(bar, cancellationToken);
public interface IRefitApiClient 
{
   [HttpPost("/api/xyz/customer/process")]
   [Headers("content-type: application/json")]
   public async Task<int> ProcessCustomers([Body] string customerIds, CancellationToken token);
}

I had to change the IEnumerable<Guid> to string as you can see above. This pretty hacky. So the issue is with the refit client after all. Even though I stringyfied guid collection on the client side, the api endpoint correctly deserializes it into List without the manual intervention.

I had a similar refit issue with passing DateTime to the api. I had to convert it to string like I did here. I must be missing some refit configuration somewhere. Surprised the models needs to be stringyfied before passing. Wasted a lot of time on this issue.

1

There are 1 answers

0
Stack Undefined On

The issue is the refit client. It stream the POST contents by default and therefore doesn't send the content-length to the api. You want the refit client to buffer the request and send all at once.

Add the Buffered: true in the Body attribute like this:

public interface IRefitApiClient 
{
   [HttpPost("/api/xyz/customer/process")]
   [Headers("content-type: application/json")]
   public async Task<int> ProcessCustomers([Body(Buffered: true)] IEnumerable<Guid> customerIds, CancellationToken token);
}

Now the model binder won't bind the parameter to null. Buffering should've been the default. I wasted a lot of time on this issue!