How to Handle a Request with application/json Content in ASP.NET Core

By FoxLearn 3/1/2025 3:15:36 AM   145
When a request comes into your action method, the ASP.NET Core framework attempts to find the correct InputFormatter to deserialize the incoming data.

However, if the request uses an unsupported content type, such as application/json, the framework might not be able to handle the deserialization automatically, and it will respond with a 415 – Unsupported Media Type error.

In this article, I’ll guide you through creating a custom InputFormatter for application/json requests, and I’ll show you how to configure the action method to correctly handle this content type.

Note: While another approach could involve manually reading the Request.Body with a StreamReader in the action method, using an InputFormatter is a cleaner and more efficient solution.

Create Your Own InputFormatter for application/json

For example, how to implement a custom InputFormatter that can process application/json requests.

using Microsoft.AspNetCore.Mvc.Formatters;

public class JsonSingleValueFormatter : InputFormatter
{
    private const string ApplicationJson = "application/json";

    public JsonSingleValueFormatter()
    {
        SupportedMediaTypes.Add(ApplicationJson);
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        try
        {
            using (var reader = new StreamReader(context.HttpContext.Request.Body))
            {
                string jsonContent = await reader.ReadToEndAsync();
                // Convert the JSON string to the target model type (parameter type in the action method)
                var model = JsonSerializer.Deserialize(jsonContent, context.ModelType);
                return InputFormatterResult.Success(model);
            }
        }
        catch (Exception ex)
        {
            context.ModelState.TryAddModelError("BodyJsonValue", $"{ex.Message} ModelType={context.ModelType}");
            return InputFormatterResult.Failure();
        }
    }

    protected override bool CanReadType(Type type)
    {
        // Define which types this formatter can handle
        return type == typeof(string) || type == typeof(int) || type == typeof(DateTime);
    }

    public override bool CanRead(InputFormatterContext context)
    {
        return context.HttpContext.Request.ContentType == ApplicationJson;
    }
}

This custom formatter reads the request body as a string, deserializes it into the specified model type, and handles any errors by updating the ModelState. If there’s a deserialization error, the framework will respond with the standard "problem details" error format.

Register the InputFormatter

Once you’ve created your custom InputFormatter, you need to register it in the InputFormatters collection.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(mvcOptions =>
{
    mvcOptions.InputFormatters.Add(new JsonSingleValueFormatter());
});

// Continue with other initialization code

Use [FromBody] to Bind the Parameter

Next, to receive the deserialized value in your action method, use the [FromBody] attribute on the parameter:

[HttpPost()]
[Consumes("application/json")]
public IActionResult Post([FromBody] string name)
{
    return Ok($"Posted value={name}");
}

This ensures that your action method is expecting application/json data in the body and deserializes it appropriately.

Now that the custom InputFormatter is registered and the action method is set up to handle JSON input, let’s test this functionality with examples.

Post a String

Send a POST request with a string in the body and set the Content-Type to application/json:

POST /Example
Content-Type: application/json

"Bob"

This will return a 200 OK response with:

Posted value Bob

Post an Integer

The custom InputFormatter looks at the parameter type and tries to convert the request body to that type. Here's an example of receiving an integer in a JSON request:

[HttpPost()]
[Consumes("application/json")]
public IActionResult Post([FromBody] int age)
{
    return Ok($"Posted age={age}");
}

Send the following request:

POST /Example
Content-Type: application/json

1

This returns a 200 OK response with:

Posted age=1

Error Response

If the body contains invalid data (e.g., a string when an integer is expected), the custom InputFormatter will populate the ModelState with the error details.

Here’s an example where we send invalid data for an integer:

POST /Example
Content-Type: application/json

"not an int"

This triggers a 400 Bad Request response with a standard problem details error:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-7d905c800640e2870c0dfacf28d89586-6ef5d985832817f4-00",
    "errors": {
        "BodyJsonValue": [
            "Input string was not in a correct format. ModelType=System.Int32"
        ]
    }
}

By creating a custom InputFormatter, you can handle requests with unsupported content types like application/json in a cleaner, more maintainable way.