how to post plain text to ASP.NET Web API endpoint?

C#asp.net Web-ApiContent Type

C# Problem Overview


I have an ASP.NET Web API endpoint with controller action defined as follows :

[HttpPost]
public HttpResponseMessage Post([FromBody] object text)

If my post request body contains plain text ( i.e. should not be interpreted as json, xml, or any other special format ), then I thought I could just include following header to my request :

Content-Type: text/plain

However, I receive error :

No MediaTypeFormatter is available to read an object of type 'Object' from content with media type 'text/plain'.

If I change my controller action method signature to :

[HttpPost]
public HttpResponseMessage Post([FromBody] string text)

I get a slightly different error message :

> No MediaTypeFormatter is available to read an object of type 'String' from content with media type 'text/plain'.

C# Solutions


Solution 1 - C#

Actually it's a shame that web API doesn't have a MediaTypeFormatter for plain text. Here is the one I implemented. It can also be used to Post content.

public class TextMediaTypeFormatter : MediaTypeFormatter
{
    public TextMediaTypeFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
    }

    public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        var taskCompletionSource = new TaskCompletionSource<object>();
        try
        {
            var memoryStream = new MemoryStream();
            readStream.CopyTo(memoryStream);
            var s = System.Text.Encoding.UTF8.GetString(memoryStream.ToArray());
            taskCompletionSource.SetResult(s);
        }
        catch (Exception e)
        {
            taskCompletionSource.SetException(e);
        }
        return taskCompletionSource.Task;
    }

    public override Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, System.Net.TransportContext transportContext, System.Threading.CancellationToken cancellationToken)
    {
        var buff = System.Text.Encoding.UTF8.GetBytes(value.ToString());
        return writeStream.WriteAsync(buff, 0, buff.Length, cancellationToken);
    }

    public override bool CanReadType(Type type)
    {
        return type == typeof(string);
    }

    public override bool CanWriteType(Type type)
    {
        return type == typeof(string);
    }
}

You need to "register" this formatter in your HttpConfig by something like that:

config.Formatters.Insert(0, new TextMediaTypeFormatter());

Solution 2 - C#

Since Web API doesn't have out of box formatter for handling text/plain, some options:

  1. Modify your action to have no parameters... reason is having parameters triggers request body de-serialization. Now you can read the request content explicitly by doing await Request.Content.ReadAsStringAsync() to get the string

  2. Write a custom MediaTypeFormatter to handle 'text/plain'... it's actually simple to write in this case and you could keep the parameters on the action.

Solution 3 - C#

In ASP.NET Core 2.0 you simply do the following :-

using (var reader = new StreamReader(Request.Body))
{
      string plainText= reader.ReadToEnd();
    
      // Do something else
    
      return Ok(plainText);
}

Solution 4 - C#

Purified version using of gwenzek's formatter employing async/await:

public class PlainTextFormatter : MediaTypeFormatter
{
    public PlainTextFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain"));
    }

    public override bool CanReadType(Type type) =>
        type == typeof(string);

    public override bool CanWriteType(Type type) =>
        type == typeof(string);

    public override async Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
    {
        var streamReader = new StreamReader(readStream);
        return await streamReader.ReadToEndAsync();
    }

    public override async Task WriteToStreamAsync(Type type, object value, Stream writeStream, HttpContent content, TransportContext transportContext, CancellationToken cancellationToken)
    {
        var streamReader = new StreamWriter(writeStream);
        await streamReader.WriteAsync((string) value);
    }
}

Please note I intentionally do not dispose StreamReader/StreamWriter, as this will dispose underlying streams and break Web Api flow. See here:

> "An implementation of this method should not close readStream upon completion. The stream will be closed independently when the HttpContent instance is disposed."

To make use of it, register while building HttpConfiguration:

protected HttpConfiguration CreateHttpConfiguration()
{
    HttpConfiguration httpConfiguration = new HttpConfiguration();
    ...
    httpConfiguration.Formatters.Add(new PlainTextFormatter());
    ...
    return httpConfiguration;
}

Solution 5 - C#

In some situations it might be simpler to let the JsonMediaTypeFormatter let do the work:

var formatter = GlobalConfiguration.Configuration.Formatters.Where(f=>f is System.Net.Http.Formatting.JsonMediaTypeFormatter).FirstOrDefault();
if (!formatter.SupportedMediaTypes.Any( mt => mt.MediaType == "text/plain" ))
    formatter.SupportedMediaTypes.Add( new MediaTypeHeaderValue( "text/plain" ) );

Solution 6 - C#

Very late to this party with a grossly simplified solution. I had success with this code in the controller method:

       public HttpResponseMessage FileHandler()
       {
        HttpResponseMessage response = new HttpResponseMessage();

        using (var reader = new StreamReader(System.Web.HttpContext.Current.Request.GetBufferedInputStream()))
        {
            string plainText = reader.ReadToEnd();
        } .....}

And on the client side, these are the Ajax options I used:

var ajaxOptions = {
url: 'api/fileupload/' + "?" + $.param({ "key": metaKey2, "File-Operation": "Remove", "removalFilePath": $removalFilePath, "Audit-Model": model, "Parent-Id": $parentId, "Audit-Id": $auditId }),

type: 'POST', processData: false, contentType: false, data: "BOB" };

Solution 7 - C#

Not a proper answer, but a quick 'n dirty workaround to unblock development...

It turns out that a quote-delimited string by itself is valid JSON. So if you know for sure that the content will always be very simple, you can wrap it in double quotes and call it application/json.

// TODO: Temporary, fix for production
HttpContent content = new StringContent($"\"{command}\"", UTF8Encoding.UTF8, "application/json");

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBaltoStarView Question on Stackoverflow
Solution 1 - C#gwenzekView Answer on Stackoverflow
Solution 2 - C#KiranView Answer on Stackoverflow
Solution 3 - C#DerekView Answer on Stackoverflow
Solution 4 - C#Vitaliy UlantikovView Answer on Stackoverflow
Solution 5 - C#mmytaView Answer on Stackoverflow
Solution 6 - C#MadogView Answer on Stackoverflow
Solution 7 - C#Paul WilliamsView Answer on Stackoverflow