Skip to content

Instantly share code, notes, and snippets.

@eibrahim
Last active December 10, 2016 15:04
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save eibrahim/10286724 to your computer and use it in GitHub Desktop.
Save eibrahim/10286724 to your computer and use it in GitHub Desktop.
A JSON formatter for ASP.NET Web API to support Ember Data
public class MyEmberJsonMediaTypeFormatter : JsonMediaTypeFormatter
{
private int _maxDepth = 256;
private JsonSerializerSettings _jsonSerializerSettings;
public MyEmberJsonMediaTypeFormatter()
{
_jsonSerializerSettings = CreateDefaultSerializerSettings();
}
public override System.Threading.Tasks.Task WriteToStreamAsync(System.Type type, object value, System.IO.Stream writeStream, System.Net.Http.HttpContent content, System.Net.TransportContext transportContext)
{
var root = GetRootFieldName(type, value);
var obj = new ExpandoObject() as IDictionary<string, Object>;
obj[root] = value;
return base.WriteToStreamAsync(type, obj as object, writeStream, content, transportContext);
}
public override Task<object> ReadFromStreamAsync(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
return Task.FromResult(ReadFromStream(type, readStream, content, formatterLogger)); ;
}
private object ReadFromStream(Type type, Stream readStream, HttpContent content, IFormatterLogger formatterLogger)
{
var root = GetRootFieldName(type);
var contentHeaders = content == null ? null : content.Headers;
// If content length is 0 then return default value for this type
if (contentHeaders != null && contentHeaders.ContentLength == 0)
return GetDefaultValueForType(type);
// Get the character encoding for the content
var effectiveEncoding = SelectCharacterEncoding(contentHeaders);
try
{
using (var reader = (new StreamReader(readStream, effectiveEncoding)))
{
var json = reader.ReadToEnd();
var jo = JObject.Parse(json);
return jo.SelectToken(root, false).ToObject(type);
}
}
catch (Exception e)
{
if (formatterLogger == null)
{
throw;
}
formatterLogger.LogError(String.Empty, e);
return GetDefaultValueForType(type);
}
}
private string GetRootFieldName(Type type, dynamic value=null)
{
//get element type if array
if (value != null && (value is IEnumerable || type.IsArray))
type = value[0].GetType();
var attrs = type.CustomAttributes.Where(x => x.AttributeType == typeof (Newtonsoft.Json.JsonObjectAttribute)).ToList();
if (attrs.Any())
{
var titles = attrs.First().NamedArguments.Where(arg => arg.MemberName == "Title")
.Select(arg => arg.TypedValue.Value.ToString()).ToList();
if (titles.Any()) return titles.First();
}
return type.Name;
}
}
[JsonObject(Title = "users")]
public class UserViewModel
{
public string username { get; set; }
public string email { get; set; }
public string password { get; set; }
}
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
GlobalConfiguration.Configuration.Formatters.Insert(0, new MyEmberJsonMediaTypeFormatter());
}
}
@PhilippRoessner
Copy link

it doesn't work here with one-to-many relationship. Ember sais: "Cannot set property 'store' of undefined" Am I missing something here?

@PhilippRoessner
Copy link

Line 58 of MyEmberJsonMediaTypeFormatter.cs thorws exception when dynamic "value" is of Type IEnumerable and not Array. Instead I tried "type = Enumerable.FirstOrDefault(value).GetType();" and it works.

@rowandh
Copy link

rowandh commented Aug 4, 2014

An IEnumerable does not guarantee indexation, but it is used as such in GetRootName. A workaround is to cast the IEnumerable to a list - just be aware of any performance concerns as this may execute a DB query.

        var materializedValue = value as IEnumerable;

        //get element type if array
        if (materializedValue != null)
        {
            var indexable = materializedValue as IList<object> ?? materializedValue.Cast<object>().ToList();
            type = indexable[0].GetType();
        }

@haldiggs
Copy link

haldiggs commented Feb 9, 2016

genius... even though its old. I made mine all work my creating custom binding DataContract objects and using a DataMember(Name = "") decorator but this makes that seem painful. I wish I had thought to write this.

@wayne-o
Copy link

wayne-o commented Dec 9, 2016

good catch @rowandh 💯 :)

@wayne-o
Copy link

wayne-o commented Dec 10, 2016

This doesn't handle empty lists - what's the best way of handling that?

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