ASP.NET Core – How to receive a request with text/plain content

When a request comes in and your action method has parameters, the framework tries to find the appropriate InputFormatter to handle deserializing the request data. There’s no built-in text/plain InputFormatter though, so when you send a request with text/plain content, it fails with a 415 – Unsupported Media Type error response.

In this article, I’ll show how to add a custom InputFormatter for text/plain requests and how to configure the action method to receive the text/plain content.

Note: The alternative approach is to manually read the Request.Body with StreamReader in the action method. I prefer and recommend the InputFormatter approach.

1 – Add your own InputFormatter that handles text/plain

Here’s an example of how to implement your own InputFormatter that handles text/plain requests.

using Microsoft.AspNetCore.Mvc.Formatters;

public class TextSingleValueFormatter : InputFormatter
{
	private const string TextPlain = "text/plain";        
	public TextSingleValueFormatter()
	{
		SupportedMediaTypes.Add(TextPlain);
	}
	public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
	{
		try
		{
			using (var reader = new StreamReader(context.HttpContext.Request.Body))
			{
				string textSingleValue = await reader.ReadToEndAsync();
				//Convert from string to target model type (this is the parameter type in the action method)
				object model = Convert.ChangeType(textSingleValue, context.ModelType);
				return InputFormatterResult.Success(model);
			}
		}
		catch(Exception ex)
		{
			context.ModelState.TryAddModelError("BodyTextValue", $"{ex.Message} ModelType={context.ModelType}");
			return InputFormatterResult.Failure();
		}
	}

	protected override bool CanReadType(Type type)
	{
		//Put whatever types you want to handle. 
		return type == typeof(string) ||
			type == typeof(int) ||
			type == typeof(DateTime);
	}
	public override bool CanRead(InputFormatterContext context)
	{
		return context.HttpContext.Request.ContentType == TextPlain;
	}
}
Code language: C# (cs)

Note: You can use InputFormatterResult.Success()/Failure() and don’t have to use the async versions of these. Also, you’ll almost always be dealing with UTF-8 as the encoding, which is the default in nearly everything. If you know you need to deal with other encodings, derive from TextInputFormatter instead of InputFormatter.

This reads the request body stream to a string and then converts it to the controller method’s parameter type (aka model type) by using Convert.ChangeType(). If an error happens, it puts error info into the ModelState and reports the failure. This results in it producing error responses with the standard “problem details” format (I’ll show an example of this at the end).

2 – Register the InputFormatter

To register your InputFormatter, add it to the InputFormatters collection in the initialization code:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(mvcOptions =>
{
    mvcOptions.InputFormatters.Add(new TextSingleValueFormatter());
});

//the rest of the initialization code
Code language: C# (cs)

3 – Use [FromBody] on the simple type parameter

To receive a single value from the request body, add a simple type parameter (such as string) and apply the [FromBody] attribute to it:

[HttpPost()]
[Consumes("text/plain")]
public IActionResult Post([FromBody]string name)
{
	return Ok($"Posted value={name}");
}
Code language: C# (cs)

Note: Optionally add [Consumes(“text/plain”)] to the method if you want to restrict it to only accepting text/plain requests.

Examples with text/plain requests

Now that the InputFormatter is created and registered, and the action is configured to receive a single value from the text/plain body, you can now send text/plain requests and see it work. I’ll show example requests below.

Post a string

Now send a request with a string value in the body and set the Content-Type to text/plain:

GET /Example

Content-Type: text/plain

Body:
BobCode language: plaintext (plaintext)

It returns a 200 (OK) response with the following output:

Posted value BobCode language: plaintext (plaintext)

Post an int

TextSingleValueFormatter looks at the parameter type (via context.ModelType) and tries to convert to that type. So here’s an example of receiving an int parameter in a text/plain request.

First, add the int parameter:

[HttpPost()]
[Consumes("text/plain")]
public IActionResult Post([FromBody]int age)
{
	return Ok($"Posted age={age}");
}
Code language: C# (cs)

Now send a request with the int value in the body:

GET /Example

Content-Type: text/plain

Body:
1Code language: plaintext (plaintext)

This returns a 200 (OK) response with the following output:

Posted age=1Code language: plaintext (plaintext)

Error response

TextSingleValueFormatter populates the ModelState with error information, which results in it returning the standard “problem details” error response. Here’s an example. Let’s say your action has an int parameter and you send in an invalid int:

GET /Example

Content-Type: text/plain

Body:
not an intCode language: plaintext (plaintext)

This returns the following 400 (Bad Request) error response:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-7d905c800640e2870c0dfacf28d89586-6ef5d985832817f4-00",
    "errors": {
        "BodyTextValue": [
            "Input string was not in a correct format. ModelType=System.Int32"
        ]
    }
}
Code language: JSON / JSON with Comments (json)

Leave a Comment