My Adventures in Coding

June 19, 2012

C# – Supporting text/plain in an MVC 4 Web API application

Filed under: .NET,c#,MVC — Brian @ 10:50 pm
Tags: , , , , , ,

If you have just created a brand new MVC 4 Web API application and when posting data with content-type: text/plain to a method in your ApiController that takes a string, you notice that the value in the string is null, this is caused by your application not having support for the media type “text/plain”.

No worries, after some digging I figured out how to get this to work.

Create the following custom MediaTypeFormatter in your application

using System;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;

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

        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 bool CanReadType(Type type)
        {
            return type == typeof(string);
        }

        public override bool CanWriteType(Type type)
        {
            return false;
        }
    }
}

Add the custom MediaTypeFormatter into the Application_Start method in your Global.asax.cs file

using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;

namespace WebApiMvc4Example
{
    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();

            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);

            // Add a reference here to the new MediaTypeFormatter that adds text/plain support
            GlobalConfiguration.Configuration.Formatters.Insert(0, new TextMediaTypeFormatter());
        }
    }
}

Now add the “FromBody” tag to your POST method that takes in a string in your ApiController

using System;
using System.Collections.Generic;
using System.IO;
using System.Web.Http;

namespace WebApiMvc4Example.Controllers
{
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get(int id)
        {
            return "value";
        }

        // POST api/values
        // Add the "FromBody" tag
        public void Post([FromBody] string value)
        {
            System.Diagnostics.Debug.WriteLine("******Woohoo: " + value);
        }

        // PUT api/values
        public void Put(string value)
        {
        }

        // DELETE api/values/5
        public void Delete(int id)
        {
        }
    }
}

That is all. So now if you start up your application:
POST to http://localhost:MYPORT/api/values with headers “content-type: text/plain” and with body “SOMETEXT” it will work just fine now.

I hope this helps!

11 Comments »

  1. Should `CanWriteType` perhaps return false given that you haven’t created the corresponding write method? As written it throws an Exception.

    Comment by Ian Mercer (@ianmercer) — June 21, 2012 @ 2:58 am | Reply

    • Good catch! Thanks for the feedback. I have updated the post. I am not using write so I did not implement an override of WriteToStreamAsync, in that case my CanWriteType method should return false.

      Comment by Brian — June 21, 2012 @ 8:26 am | Reply

  2. I copied the code and added the formatter into my project but for some reason I get a compiler error that states:

    ReadFromStreamAsync(System.Type, System.IO.Stream, System.Net.Http.Headers.HttpContentHeaders, System.Net.Http.Formatting.IFormatterLogger)’: no suitable method found to override

    Any ideas what would cause this? A bad .dll?

    Comment by Corey Gleisner — August 21, 2012 @ 12:40 pm | Reply

    • Ah, I see the issue. When I originally wrote this post it was for MVC 4 Release Candidate. You must be using the final release of MVC 4. I have updated the post to work now with MVC 4 Final Release.

      Basically the change was:

      In MVC 4 RC version, the method to override in the custom TextMediaTypeFormatter:

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

      However, now in the final release of MVC 4 the method to override in the custom TextMediaTypeFormatter:

      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;
      }
      

      Comment by Brian — August 22, 2012 @ 9:59 am | Reply

  3. Great job Brian! Saved me!

    Comment by Ryan — August 30, 2012 @ 6:20 am | Reply

  4. Great post, but i am wondering how to this the other way around so i want to serve out a text/plain or a binary/octet so set the response header to this but i am really stuck with this can you help?

    Comment by Sander — September 19, 2012 @ 7:47 am | Reply

  5. I am using your formatter with the folloiwng request header.
    POST http://127.0.0.1:81/public/api/UserRegistration/Register HTTP/1.1
    Accept: */*
    Origin: http://127.0.0.2:81
    Accept-Language: en-IN
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)
    Host: 127.0.0.1:81
    Content-Length: 366
    Connection: Keep-Alive
    Pragma: no-cache

    Salutation=Mr&FirstName=jsdkljfaaksjdf&LastName=askjdfkajsdf&EmailAddress=asjdfkdfa%40adjsf.com&Phone=9999999999&Phone2=&City=asdfja&State=Alabama&Zip=44444&HowDidHearAboutUS=Magazinearticle&UserName=jasdkjfa&Password=pass1&ConfirmPassword=pass1&SecurityQuestion=NativeTown&SecurityAnswer=asdfasdf&UserType=EveryPennyIndividual&RefererName=&RefererEmail=&RefererOrg=

    But this doesn’t work.

    Comment by Rajiv — October 24, 2012 @ 8:37 am | Reply

    • For this to work you would need to provide the request header:

      content-type: text/plain

      Comment by Brian — October 24, 2012 @ 8:58 am | Reply

  6. Fantastic! Worked like a charm!

    Comment by Eric — December 26, 2012 @ 12:43 pm | Reply

  7. Thanks Brian – great article. Helped me out of a hole šŸ˜‰

    Comment by TriSys (@TriSys) — March 17, 2013 @ 12:44 pm | Reply

  8. Awesome!! Thank You!!

    Comment by Luca — July 22, 2014 @ 8:45 am | Reply


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: