I have a third-party proprietary application, for which I need to write an API endpoint in my ASP.NET Core 5.0 web API application.
The third party application sends out an HTTP post request, with only binary data in the request body, alongside the content type application/x-www-form-urlencoded
or, sometimes, application/octet-stream
(kind of random, but the data is the same).
My action handler looks like this:
[Route("~/Validation")]
[ApiController]
public class ValidationController : ControllerBase
{
[HttpPost("{requestId}")]
[Consumes(@"application/octet-stream", @"application/x-www-form-urlencoded")]
[Produces(@"application/octet-stream")]
public async Task<IActionResult> Validation_Post([FromRoute] string requestId)
{
byte[] rawRequestBody = Array.Empty<byte>();
{
long streamInitialPos = 0;
if (Request.Body.CanSeek) // rewind for this read.
{
streamInitialPos = Request.Body.Position;
Request.Body.Seek(0, SeekOrigin.Begin);
}
using (var ms = new MemoryStream())
{
await Request.Body.CopyToAsync(ms);
rawRequestBody = ms.ToArray() ?? throw new NullReferenceException();
}
if (Request.Body.CanSeek) // rewind to initial position.
Request.Body.Seek(streamInitialPos, SeekOrigin.Begin);
}
// TODO: Handle rawRequestBody data.
return new FileContentResult(new byte[] { 1 }, @"application/octet-stream")
{
EnableRangeProcessing = true,
LastModified = DateTime.UtcNow
};
}
When the third-party application is sending its HTTP post request to my API endpoint, my API application crashes with a System.ArgumentException
:
Microsoft.AspNetCore.Server.IIS.Core.IISHttpServer: Error: Connection ID "18374686481282236432", Request ID "80000011-0000-ff00-b63f-84710c7967bb": An unhandled exception was thrown by the application.
System.ArgumentException: The key '[omitted binary data]' is invalid JQuery syntax because it is missing a closing bracket. (Parameter 'key')
at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryKeyValuePairNormalizer.NormalizeJQueryToMvc(StringBuilder builder, String key)
at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryKeyValuePairNormalizer.GetValues(IEnumerable`1 originalValues, Int32 valueCount)
at Microsoft.AspNetCore.Mvc.ModelBinding.JQueryFormValueProviderFactory.AddValueProviderAsync(ValueProviderFactoryContext context)
at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.CreateAsync(ActionContext actionContext, IList`1 factories)
at Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider.TryCreateAsync(ActionContext actionContext, IList`1 factories)
at Microsoft.AspNetCore.Mvc.Controllers.ControllerBinderDelegateProvider.<>c__DisplayClass0_0.<<CreateBinderDelegate>g__Bind|0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Server.IIS.Core.IISHttpContextOfT`1.ProcessRequestAsync()
Microsoft.AspNetCore.Hosting.Diagnostics: Information: Request finished HTTP/1.1 POST http://localhost:10891/validation/dummy application/x-www-form-urlencoded 11072 - 500 - - 164.0024ms
The logs are showing that the correct route action is being used.
How do I disable automatic model binding for only this specific action handler?
Reminder: I cannot do any changes to the third-party application. I have to handle what I receive. I know the request content type is wrong. Please don't make any notes in that regard.
Edit: I have found the surface-level cause for this error. When I remove [FromRoute] string requestId
from the function signature, the error will not occur. When I reintroduce it, the error occurs again.
Does not work (causes ASP.NET Core internal exception):
public async Task<IActionResult> Validation_Post([FromRoute] string requestId)
Does work:
public async Task<IActionResult> Validation_Post()
However, I need to access the route variable through Request.RouteValues["requestId"]
.
Anyway, the question still stands: How do I disable automatic model binding for only this specific action handler?
Controller derives from ControllerBase and adds support for views, so it's for handling web pages, not web API requests. If the same controller must support views and web APIs, derive from Controller . The following table contains examples of methods in ControllerBase .
[FromBody] attributeThe ASP.NET Core runtime delegates the responsibility of reading the body to an input formatter. Input formatters are explained later in this article. When [FromBody] is applied to a complex type parameter, any binding source attributes applied to its properties are ignored.
The FromForm attribute is for incoming data from a submitted form sent by the content type application/x-www-url-formencoded while the FromBody will parse the model the default way, which in most cases are sent by the content type application/json , from the request body.
I think you’ve actually figured out the solution already—or, at least, identified all of the key components. Allow me to walk through them so you can assemble them into a solution.
Model binding only occurs when you have parameters on your action—otherwise there is no model to bind to. This could be an actual model, such as a POCO, or simply a value type, such as the string parameter you have here; the same basic process applies to both. That’s why you don’t receive the error when you remove the parameter—and also why you’ve effectively answered your question already.
The only purpose or benefit to having a parameter is for it to engage in model binding. So if you want to disable model binding, there’s no reason to maintain the parameter. Without it, you can still use the Request
property to extract request values, including form fields, query string parameters, route variables, request headers, &c. So, in this case, you can still call e.g., Request.RouteValues["requestId"]
within your action, as you noted. Making calls directly to the RouteValueDictionary
won’t trigger the call to the JQueryKeyValuePairNormalizer
class, so you won’t encounter the same exception.
That said, depending on how your route and route parameters are defined, you may encounter issues if you’re e.g., relying on this parameter to disambiguate between other overloads of your action, so that’s worth being aware of.
To return to the core error, however, I’d also recommend evaluating your request data on failed requests to ensure there isn’t anything that might be confused with a field name containing a malformed indexer. From what you describe, I wouldn’t expect that to be the case, unless it was somehow trying to parse the binary data as key/value pairs expected of form data, possibly due to the errant content type. Regardless, the line of code that’s throwing the error occurs, specifically, when there is a field containing a [
without a corresponding ]
. For example, it should occur with the following query string:
?key[0=5
If something like that is a consistent fixture of your incoming requests, that’s likely the culprit. Obviously, you can’t do anything about it since you don’t control the client, but it’d be useful to isolate so you can offer guidance for future implementations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With