How to Add a custom InputFormatter in ASP.NET Core

By FoxLearn 2/10/2025 7:44:04 AM   50
How to implement a custom InputFormatter in ASP.NET Core that handles a non-standard content type, such as application/xml, to deserialize the request body into a MyModel object:

If you need a custom InputFormatter that processes application/xml requests. This formatter will convert the XML data into a MyModel object.

Steps:

  1. Subclass the InputFormatter class.
  2. Specify the Content-Type the formatter will handle (e.g., application/xml).
  3. Deserialize the request body into the model.
  4. Register the formatter in the application's services.

Custom InputFormatter

For example:

using Microsoft.AspNetCore.Mvc.Formatters;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

public class XmlModelInputFormatter : InputFormatter
{
    public XmlModelInputFormatter()
    {
        // Specify the Content-Type(s) this InputFormatter can handle
        SupportedMediaTypes.Add("application/xml");
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        using (var reader = new StreamReader(context.HttpContext.Request.Body, Encoding.UTF8))
        {
            var xmlContent = await reader.ReadToEndAsync();
            
            // Deserialize the XML into your model (assumes MyModel is your target model)
            var serializer = new XmlSerializer(typeof(MyModel));
            using (var stringReader = new StringReader(xmlContent))
            {
                var result = (MyModel)serializer.Deserialize(stringReader);
                return InputFormatterResult.Success(result);
            }
        }
    }

    protected override bool CanReadType(Type type)
    {
        // Specify the model type this InputFormatter can handle
        return type == typeof(MyModel);
    }
}

Register the Formatter

In the Program.cs or Startup.cs, add the custom input formatter to the application's services:

var builder = WebApplication.CreateBuilder(args);

// Add custom InputFormatter to the service collection
builder.Services.AddControllers(options =>
{
    options.InputFormatters.Add(new XmlModelInputFormatter());
});

var app = builder.Build();
app.MapControllers();
app.Run();

Use the Formatter in a Controller

[HttpPost()]
[Consumes("application/xml")]  // Only accept XML Content-Type for this action
public IActionResult Post([FromBody] MyModel model)
{
    return Ok($"Received model with Id: {model.Id} and Name: {model.Name}");
}

You can test this functionality by sending the following XML request to your API endpoint (e.g., /api/my-model):

POST /api/my-model

Content-Type: application/xml

<MyModel>
  <Id>123</Id>
  <Name>Test Model</Name>
</MyModel>

Response:

If the deserialization is successful, you would receive an OK response with a message like:

Received model with Id: 123 and Name: Test Model

Handling Errors:

To handle errors during deserialization, you can modify the formatter to check for invalid XML and add custom error messages:

public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
    try
    {
        using (var reader = new StreamReader(context.HttpContext.Request.Body, Encoding.UTF8))
        {
            var xmlContent = await reader.ReadToEndAsync();
            var serializer = new XmlSerializer(typeof(MyModel));
            using (var stringReader = new StringReader(xmlContent))
            {
                var result = (MyModel)serializer.Deserialize(stringReader);
                return InputFormatterResult.Success(result);
            }
        }
    }
    catch (Exception ex)
    {
        context.ModelState.TryAddModelError("Body", "Invalid XML format.");
        return InputFormatterResult.Failure();
    }
}

If the XML is invalid, the API would return a 400 Bad Request response with error details:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "some-trace-id",
    "errors": {
        "Body": [
            "Invalid XML format."
        ]
    }
}

This way, you can customize how your API deserializes requests and provide better error handling for non-standard content types like application/xml.