Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I disable automatic model binding for a specific ASP.NET Core 5.0 Web API action?

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?

like image 549
burnersk Avatar asked Oct 15 '22 23:10

burnersk


People also ask

What is the difference between controller and ControllerBase?

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 .

What is FromBody in .NET Core?

[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.

What is FromForm?

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.


1 Answers

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.

Disabling Model Binding

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.

Extracting route values without model binding

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.

Potential route ambiguity

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.

The underlying problem

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.

like image 157
Jeremy Caney Avatar answered Oct 18 '22 13:10

Jeremy Caney