Table of Contents

ASP.NET MVC formatters

This library provides MessagePack-based formatters for ASP.NET MVC, offering significant performance improvements over the default JSON protocol.

Benefits

  • Smaller Payloads: MessagePack produces significantly smaller payloads compared to JSON
  • Faster Serialization: Binary serialization is typically faster than text-based formats
  • Type Safety: Leverages MessagePack's type-safe serialization system
  • NativeAOT Compatible: Works seamlessly with .NET Native AOT compilation

Installation

Install the NuGet package:

Nerdbank.MessagePack.AspNetCoreMvcFormatter NuGet package

Add MessagePackInputFormatter and/or MessagePackOutputFormatter to your input and/or output formatter collections respectively, as demonstrated in the configuration sample below:

void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddMvcOptions(option =>
    {
        option.OutputFormatters.Clear();
        option.OutputFormatters.Add(new MessagePackOutputFormatter(Witness.GeneratedTypeShapeProvider));
        option.InputFormatters.Clear();
        option.InputFormatters.Add(new MessagePackInputFormatter(Witness.GeneratedTypeShapeProvider));
    });
}

// Generate shapes for types commonly used in communicating errors in ASP.NET Core MVC.
[GenerateShapeFor<ProblemDetails>]
[GenerateShapeFor<SerializableError>]
[GenerateShapeFor<string[]>]
partial class Witness;

Usage

Server

Add [Produces("application/x-msgpack")] to the action or controller that returns msgpack-encoded data.

[Route("api/v1/[controller]")]
[Produces("application/x-msgpack")]
[ApiController]
public partial class PersonController : ControllerBase
{
    public ActionResult<IEnumerable<Person>> Get()
    {
        return this.Ok(new Person[]
        {
            new(1, "Person 1"),
            new(2, "Person 2"),
        });
    }

    // GET: api/v1/person/{slug}
    [HttpGet("{id}")]
    public ActionResult<Person> Get(int id)
    {
        if (id > 10)
        {
            this.ModelState.AddModelError("id", "ID must be 10 or less.");
            return this.BadRequest(this.ModelState);
        }

        return this.Ok(new Person(id, $"Person {id}"));
    }

    [GenerateShape]
    public partial record Person(int Id, string Name);

    // Add an attribute for each top-level type that must be serializable
    // That does not have have its own [GenerateShape] attribute on it.
    // Here, we add `int` because it's taken as a parameter type for an action.
    // Although strictly speaking `int` is already covered implicitly because it
    // also appears as a property on Person.
    [GenerateShapeFor<Person[]>]
    [GenerateShapeFor<int>]
    partial class Witness;
}

Client

The JavaScript client should send data with application/x-msgpack as the HTTP Content-Type header. The Accept header should include this same content-type so that the server will utilize the MessagePack formatter to send the optimized data format back to the client.

<html>
<head>
    <script src="https://unpkg.com/msgpack-lite@0.1.26/dist/msgpack.min.js"></script>
</head>
<body>
    <h1>Welcome to ASP.NET MVC!</h1>
    <p>This is a sample view.</p>
    <input type="text" id="personId" placeholder="Enter person ID (optional)">
    <button id="fetchBtn">Fetch Person Data</button>
    <div id="errorMessage" style="color: red;"></div>
    <div id="result"></div>
    <script>
        document.getElementById('fetchBtn').addEventListener('click', async () => {
            const id = document.getElementById('personId').value.trim();
            const url = id ? `/api/v1/person/${id}` : '/api/v1/person';
            document.getElementById('errorMessage').textContent = ''; // Clear previous error
            try {
                const response = await fetch(url, {
                    headers: { 'Accept': 'application/x-msgpack' }
                });
                const buffer = await response.arrayBuffer();
                const data = msgpack.decode(new Uint8Array(buffer));
                if (!response.ok) {
                    // Assuming data is ModelState with errors, e.g., { id: ['ID must be 10 or less.'] }
                    const errorMsg = data?.id?.[0] || 'Unknown error';
                    document.getElementById('errorMessage').textContent = errorMsg;
                    document.getElementById('result').innerHTML = '';
                } else {
                    document.getElementById('result').innerHTML = '<pre>' + JSON.stringify(data, null, 2) + '</pre>';
                }
            } catch (error) {
                document.getElementById('errorMessage').textContent = 'Error: ' + error.message;
                document.getElementById('result').innerHTML = '';
            }
        });
    </script>
</body>
</html>