ASP.NET - How to make a controller action receive text/plain content as a string - which also works in swagger - updated 2023

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 than I thought it would be. In this post I first 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 - as there are no parameters for the method. The above also seems 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] annotation:

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

In order to achieve the above you need to do a bit more work. Down below is a simplified example on how to do this, 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

The first step is to create a new implementation of the input formatter. In the code snippet below we create a TextPlainInputFormatter class that ASP.NET can use to understand how to model bind text/plain requests. An input formatter defines a CanRead method that determines if it can deserialize the input and a ReadRequestBodyAsync method that actually deserializes 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: In the above we 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 or wherever you configure our dependency injection in your ASP.NET project:

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

As Tometchy points out below in the comments, the above is for asp.net core 3.0 and above. If you are running 2.1 or older you should be using AddMvc instead:

services.AddMvc(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 to make a request in the swagger UI, which makes it a breeze to use swagger.

The result

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

Text Plain swagger endpoint in asp.net core

That is it!

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!