Skip to content

Instantly share code, notes, and snippets.

@ewoo
Last active October 19, 2016 04:20
Show Gist options
  • Save ewoo/6083179 to your computer and use it in GitHub Desktop.
Save ewoo/6083179 to your computer and use it in GitHub Desktop.
A custom MediaTypeFormatter for wrangling incoming form-url-encoded snake case (ruby-style) field names into regular property names such that ASP.NET Web API's plumbing can properly bind them to .NET model objects.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Http.ModelBinding;
namespace WebApiExample
{
/// <summary>
/// Acts much like the built-in JQueryMvcFormUrlEncodedFormatter but instead, escapes snake-case
/// property names and performs parameter binding with models having CamelCase property names.
/// </summary>
public class SnakCaseFormUrlEncodedMediaTypeFormatter : FormUrlEncodedMediaTypeFormatter
{
public override bool CanReadType(Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
return true;
}
private static FormDataCollection RewritePropertyNames(FormDataCollection formData)
{
var keypairs = new List<KeyValuePair<string, string>>();
foreach (var pair in formData)
{
// Remove underscores from property names. That's it!!!
var propertyName = pair.Key.Replace("_", "");
keypairs.Add(new KeyValuePair<string, string>(propertyName, pair.Value));
}
return new FormDataCollection(keypairs);
}
// This method is a slightly modifie version of JQueryMvcFormUrlEncodedFormatter's implementation.
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content,
IFormatterLogger formatterLogger)
{
if (type == (Type) null)
throw new ArgumentNullException("type");
if (readStream == null)
throw new ArgumentNullException("readStream");
if (base.CanReadType(type))
return base.ReadFromStreamAsync(type, readStream, content, formatterLogger);
// The following expression could probably be better expressed by someone who's saavier with Tasks.
return base.ReadFromStreamAsync(typeof (FormDataCollection), readStream, content, formatterLogger)
.ContinueWith(
(obj =>
{
var local = RewritePropertyNames((FormDataCollection) obj.Result);
try
{
return FormDataCollectionExtensions.ReadAs(local, type, string.Empty,
this.RequiredMemberSelector,
formatterLogger);
}
catch (Exception ex)
{
if (formatterLogger == null)
{
throw;
}
else
{
formatterLogger.LogError(string.Empty, ex);
return MediaTypeFormatter.GetDefaultValueForType(type);
}
}
}), new CancellationToken());
}
}
}
@ewoo
Copy link
Author

ewoo commented Jul 25, 2013

Here's the wire-up code:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Other config stuff here... 
        config.Formatters.XmlFormatter.MediaTypeMappings.Add(new UriPathExtensionMapping("xml", "application/xml"));
        config.Formatters.JsonFormatter.MediaTypeMappings.Add(new UriPathExtensionMapping("json", "application/json"));

        // Hook-up custom media type formatter here.
        config.Formatters.Insert(0, new SnakCaseFormUrlEncodedMediaTypeFormatter());
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment