Data Formatting in ASP.NET Core Web API

ASP.NET Core Web API provides flexible mechanisms for formatting data sent to and received from clients. This allows your API to communicate effectively using various formats like JSON, XML, and others.

Content Negotiation

Content negotiation is the process where the server and client agree on the format of the data being exchanged. ASP.NET Core Web API uses the Accept and Content-Type headers to facilitate this.

Supported Formatters

By default, ASP.NET Core Web API includes formatters for common data types:

Configuring Formatters

You can configure which formatters are used and how they behave in your Program.cs (or Startup.cs in older versions) file.

Adding/Removing Formatters

To customize the available formatters, you can modify the MvcNewtonsoftJsonOptions or MvcXmlOptions. For instance, to explicitly enable JSON:

Program.cs (Minimal API Style)


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews()
    .AddNewtonsoftJson(options =>
    {
        // Configure JSON serializer settings here if needed
        // options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
    });

// If you want to explicitly remove XML:
// builder.Services.AddControllers()
//     .AddNewtonsoftJson()
//     .AddXmlSerializerFormatters() // This line would add XML, so you might remove it or not add it
//     .AddMvcOptions(options => options.OutputFormatters.RemoveType<XmlDataContractSerializerOutputFormatter>());


var app = builder.Build();

// ... configure pipeline ...

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();
                

Customizing JSON Serialization

You can fine-tune how JSON is serialized using options provided by Newtonsoft.Json:

Customizing JSON Options


builder.Services.AddControllersWithViews().AddNewtonsoftJson(options =>
{
    options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore; // Don't include null properties
    options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(); // Use camelCase for property names
});
                

Working with Data Formats in Controllers

Your controllers can produce and consume different data formats. By default, the API will try to match the client's Accept header.

Returning Different Formats

You can explicitly specify the format or rely on content negotiation.

Example Controller Action


using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;

public class ProductsController : ControllerBase
{
    // GET api/products
    [HttpGet]
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }

    // GET api/products/5
    [HttpGet("{id}")]
    // Attribute routing can specify formats, but content negotiation is primary
    [Produces("application/json")] // Explicitly declares it can produce JSON
    public string Get(int id)
    {
        return $"Product {id}";
    }

    // You can also return OkObjectResult with content negotiation
    [HttpGet("negotiated/{id}")]
    public IActionResult GetNegotiated(int id)
    {
        var product = new { Id = id, Name = $"Product {id}", Price = 19.99 };
        return Ok(product); // Will be serialized based on Accept header
    }

    // Returning XML explicitly if needed (requires XML formatter enabled)
    [HttpGet("xml/{id}")]
    [Produces("application/xml")]
    public IActionResult GetXml(int id)
    {
        var product = new { Id = id, Name = $"Product {id}", Price = 19.99 };
        return Ok(product);
    }
}
                

Consuming Data (Model Binding)

When a client sends data, ASP.NET Core Web API uses model binders to deserialize the incoming request body into C# objects. This works seamlessly with supported formatters.

Consuming Data in an Action


using Microsoft.AspNetCore.Mvc;

public class OrdersController : ControllerBase
{
    public class OrderItem
    {
        public int ProductId { get; set; }
        public int Quantity { get; set; }
    }

    // POST api/orders
    [HttpPost]
    // The [FromBody] attribute is often implicit for complex types in the request body
    // but can be explicitly used for clarity.
    public IActionResult CreateOrder([FromBody] OrderItem order)
    {
        if (!ModelState.IsValid)
        {
            return BadRequest(ModelState);
        }

        // Process the order...
        return CreatedAtAction(nameof(GetOrder), new { id = 123 }, order); // Example response
    }

    private IActionResult GetOrder(int id)
    {
        // Placeholder for GET order logic
        return Ok();
    }
}
                

Best Practices