WebAPI Formatting
Json formatting
See Json.net.
- [JsonIgnore] to ignore properties
- Use [DataContract]/[DataMember] for opt-in model
- Translate .net conventional PascalCasing to javascript-friendly camelCasing:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ContractResolver =
new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver();
Full error stack in your json:
#if DEBUG
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.LocalOnly;
#endif
Circular references
If an object has reference properties (Order.Product) by default you'll get repeated nested objects. If there's a back-reference, you get a Newtonsoft.Json.JsonSerializationException: "Self referencing loop detected for property 'x' with type 'z'". To create proper json references ("$id" properties), use:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.PreserveReferencesHandling =
Newtonsoft.Json.PreserveReferencesHandling.All;
For xml [DataContract(IsReference=true)]
Content Negotiation
- HttpConfiguration (GlobalConfiguration.Configuration) has
- Services.GetContentNegotiator(): an IContentNegotiator (normally DefaultContentNegotiator)
- Formatters: list of MediaTypeFormatters (or derived BufferedMediaTypeFormatters) which
- CamWrite/CanRead
- SupportedMediaTypes: list of mime types that are matched to Request Accept header (eg application/json)
- MediaTypeMappings: list of mappers. By default the mime type is read from the Request Accept or the Content-Type HTTP headers.
If you add mappers, they intercept it. Built-in mappers are RequestHeader, QueryString ("?format=json") and UrlPathExtension (".json")
Using a urlPathExtension- add this to the routes: (NB- {id}.{ext} will make IIS look for a physical file and 404)
config.Routes.MapHttpRoute(
name: "DefaultApi With Extensions",
routeTemplate: "api/{controller}.{ext}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
And in Application_Start/ WebApiConfig.Register:
// GET /Category.json/1 returns json (*requires route with {ext})
GlobalConfiguration.Configuration.Formatters.JsonFormatter.MediaTypeMappings.Add(
new System.Net.Http.Formatting.UriPathExtensionMapping("json", "application/json")
);
// GET /Category/1?format=json returns json
GlobalConfiguration.Configuration.Formatters.JsonFormatter.MediaTypeMappings.Add(
new System.Net.Http.Formatting.QueryStringMapping("format", "json", "application/json")
);
A simple CSV formatter (does not recurse reference properties):
public class CsvFormatter : System.Net.Http.Formatting.BufferedMediaTypeFormatter
{
public CsvFormatter()
{
SupportedMediaTypes.Add(new System.Net.Http.Headers.MediaTypeHeaderValue("text/csv"));
}
/// <summary>
/// <code>GlobalConfiguration.Configuration.Formatters.Add(new CsvFormatter(new QueryStringMapping("format", "csv", "text/csv")));</code>
/// </summary>
public CsvFormatter(System.Net.Http.Formatting.MediaTypeMapping mediaTypeMapping)
: this()
{
MediaTypeMappings.Add(mediaTypeMapping);
}
public override bool CanReadType(Type type)
{
return false; //writer, not reader
}
public override bool CanWriteType(Type type)
{
return true;
//specific generic type
//if (type == typeof(T))
//{
// return true;
//}
//else
//{
// var enumerableType = typeof(IEnumerable<T>);
// return enumerableType.IsAssignableFrom(type);
//}
}
public override void WriteToStream(Type type, object value, Stream writeStream, HttpContent content)
{
using (var writer = new StreamWriter(writeStream))
{
//simple type
if (value is string || value.GetType().IsValueType)
{
WriteItem(value, writer);
}
else
{
//enumerable
var items = value as IEnumerable;
if (items != null)
{
foreach (var item in items)
{
WriteItem(item, writer);
}
}
else
{
//single item
var item = value;
WriteItem(item, writer);
}
}
}
writeStream.Close();
}
private void WriteItem(Object obj, TextWriter writer)
{
var type = obj.GetType();
if (type.IsValueType || type == typeof(string))
{
writer.WriteLine(Escape(obj));
return;
}
if (_properties == null)
{
_properties = type.GetProperties();
}
foreach (var propertyInfo in _properties)
{
var propertyValue = Escape(propertyInfo.GetValue(obj));
writer.Write(propertyValue + ",");
}
writer.WriteLine();
}
static readonly char[] SpecialChars = { ',', '\n', '\r', '"' };
private PropertyInfo[] _properties;
private string Escape(object o)
{
if (o == null)
return "";
if (o is DateTime)
{
var date = (DateTime)o;
return date.ToString("O"); //ISO 8601
}
var field = o.ToString();
if (field.IndexOfAny(SpecialChars) != -1)
return String.Format("\"{0}\"", field.Replace("\"", "\"\""));
return field;
}
}