Last active
December 10, 2016 15:04
-
-
Save eibrahim/10286724 to your computer and use it in GitHub Desktop.
A JSON formatter for ASP.NET Web API to support Ember Data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[JsonObject(Title = "users")] | |
public class UserViewModel | |
{ | |
public string username { get; set; } | |
public string email { get; set; } | |
public string password { get; set; } | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
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.
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();
}
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.
good catch @rowandh 💯 :)
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
it doesn't work here with one-to-many relationship. Ember sais: "Cannot set property 'store' of undefined" Am I missing something here?