Asp.net core - how to make a controller action receive text/plain content as a string - which also works in swagger

I have recently had to make an endpoint in asp.net core that receives text/plain content - or raw text as you may call it. I assumed that this was straight forward in Asp.net but it turned out to be much harder. In this post I first I present the solutions I went through and at the end I describe what I ended up with.

I first found this simple solution on stack overflow:

[HttpPost]
public async Task<ActionResult<int>> Process()
{
    string jsonString;
    using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
    {
        jsonString = await reader.ReadToEndAsync();
    }

The above works perfectly, however it has some drawbacks. If you use swagger it will not pick up that this endpoint has an input (body). Just as you cannot read from the method definition that this method has an hidden input (Request.Body), so can swagger not. The above also seem like a hack, but it does get the job done in a very simple way.

What I wanted to achieve was something like the following using the [FromBody] annoation:

[HttpPost("TryItOut")]
public string TryItOut([FromBody] string body)
{
    return body;
}

In order to achieve the above you need to do a bit more work. I have made my own simplified example on how to do this down below, by creating an input formatter for raw text (content-type text/plain). You can also read Rick Strahl's blog where he goes much more in depth with this.

Creating a new input formatter

First step is to create a new imeplementation of the input formatter. In the code snippet below I have created a TextPlainInputFormatter class that asp.net core can use to understand how to model bind text/plain requests. An input formatter defines a CanRead method that determines if it can deserialise the input and a ReadRequestBodyAsync method that actually deserialises the data.

public class TextPlainInputFormatter : InputFormatter
{
    private const string ContentType = "text/plain";

    public TextPlainInputFormatter()
    {
        SupportedMediaTypes.Add(ContentType);
    }

    public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
    {
        var request = context.HttpContext.Request;
        using (var reader = new StreamReader(request.Body))
        {
            var content = await reader.ReadToEndAsync();
            return await InputFormatterResult.SuccessAsync(content);
        }
    }

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

Note: I choose that the content type should start with "text/plain" as some systems sometimes provide a charset such as: text/plain;charset=UTF-8.

Registering the input formatter

Next we need to add our new InputFormatter to the list of formatters. This you should do in your Startup.cs file in your asp.net core project:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers(o => o.InputFormatters.Insert(o.InputFormatters.Count, new TextPlainInputFormatter()));
}

Using the formatter

Third step is to make an endpoint, you can now use the [FromBody] annotation together with string to receive plain text! Which means you can reuse the same input formatter for several endpoints. Here is how it is used:

[HttpPost("TryItOut")]
[Consumes("text/plain")]
public string TryItOut([FromBody] string body)
{
    return body;
}

Consumes("text/plain") makes swagger automatically select text/plain instead of application/json when you try and make a request in the swagger UI.

The result

Swagger will also pick this up and suggest you use text/plain for this endpoint:

Text Plain swagger endpoint in aspnet core

I hope this helped you, I do not understand why this has to be so much work. If you have any comments please leave them down below in the comments section!