Return camelCase property names from API controller
I am running Umbraco 7.4.1 and have a custom property editor to search reviews (listview nodes) and pick some of the result (similar to the MNTP, but not with the whole tree structure and more details for each "Review" node.
For that purpose I am using an API Controller to return data from the Examine index.
Model:
public class Review
{
public int Id { get; set; }
public string Name { get; set; }
public string City { get; set; }
public DateTime Date { get; set; }
public string Destination { get; set; }
public string Header { get; set; }
public string Hotel { get; set; }
public int HotelRating { get; set; }
public int Rating { get; set; }
public bool ShowComment { get; set; }
public string Text { get; set; }
public string Photo { get; set; }
}
public class ReviewSearchResult
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public string Path { get; set; }
public Review Review { get; set; }
}
API Controller
public class ReviewsController : UmbracoAuthorizedApiController //UmbracoApiController //UmbracoAuthorizedJsonController
{
//[System.Web.Http.HttpGet]
//[CacheOutput(ServerTimeSpan = 86400)]
public IEnumerable<ReviewSearchResult> GetSearchReviews(int rootId, string keyword, bool onlyCommentsEnabled = false, int limit = 200)
{
string searcherName = GetSearcherName(rootId);
var searcher = ExamineManager.Instance.SearchProviderCollection[searcherName];
var criteria = searcher.CreateSearchCriteria();
var results = searcher.Search(criteria.RawQuery(string.Format("nodeName:*{0}* reviewName:*{0}* reviewCity:*{0}* reviewDestination:*{0}* reviewHotel:*{0}*", keyword)));
List<ReviewSearchResult> reviewResults = new List<ReviewSearchResult>();
foreach (var r in results.OrderBy(x => x.Score).Take(limit))
{
bool showComment = r.Fields.ContainsKey("reviewShowComment") ? r.Fields["reviewShowComment"] != "0" : false;
if(onlyCommentsEnabled && !showComment)
{
continue;
}
Review review = new Review
{
Name = r.Fields.ContainsKey("reviewName") ? r.Fields["reviewName"] : "", //nodeName
City = r.Fields.ContainsKey("reviewCity") ? r.Fields["reviewCity"] : "",
Date = DateTime.Parse(r.Fields.ContainsKey("reviewDate") ? r.Fields["reviewDate"] : DateTime.Now.ToString()),
Destination = r.Fields.ContainsKey("reviewDestination") ? r.Fields["reviewDestination"] : "",
Header = r.Fields.ContainsKey("reviewHeader") ? r.Fields["reviewHeader"] : "",
Hotel = r.Fields.ContainsKey("reviewHotel") ? r.Fields["reviewHotel"] : "",
HotelRating = int.Parse(r.Fields.ContainsKey("reviewHotelRating") ? r.Fields["reviewHotelRating"] : "0"),
Rating = int.Parse(r.Fields.ContainsKey("reviewRating") ? r.Fields["reviewRating"] : "0"),
Text = r.Fields.ContainsKey("reviewText") ? r.Fields["reviewText"] : "",
Photo = r.Fields.ContainsKey("reviewPhoto") ? r.Fields["reviewPhoto"] : "",
ShowComment = showComment
};
reviewResults.Add(new ReviewSearchResult
{
Id = int.Parse(r.Fields.ContainsKey("id") ? r.Fields["id"] : "0"),
Name = r.Fields.ContainsKey("nodeName") ? r.Fields["nodeName"] : "",
Url = Umbraco.NiceUrl(int.Parse(r.Fields.ContainsKey("id") ? r.Fields["id"] : "0")),
Path = r.Fields.ContainsKey("path") ? r.Fields["path"] : "",
Review = review
});
}
// camelcase properties when it is json response
var jsonFormatter = GlobalConfiguration.Configuration.Formatters.OfType<JsonMediaTypeFormatter>().First();
var settings = jsonFormatter.SerializerSettings;
settings.Formatting = Formatting.Indented;
settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
return reviewResults;
}
...
}
When using UmbracoApiController or UmbracoAuthorizedApiController it returns camelCase property names, but with UmbracoAuthorizedJsonController it seems to ignore that part and returns the default casing of the property names.
How can I use UmbracoAuthorizedJsonController and still return camelCase property names?
There's a few things you'll need to understand in order to know what is going on here. Firstly, there are global formatters and then there are controller specific formatters. By default global formatters will apply to ALL controllers in your solution - but if we allowed for developers to change the formatting of our data to whatever they want then everything would probably just break. Therefore for Umbraco's purposes - we apply controller specific formatters so we know exactly how the data will be formatted & cannot be tampered with so the back office doesn't break.
As you can see this has a few attributes applied to it: Ensures that it's routed as a back office request, ensures browser cache is disabled for all responses, appends a custom header to the response to provide the user's timeout value and lastly ensures that the request is authorized to access the controller. As you can see there is absolutely nothing going on with Formatters here thus Global formatters will be applied to any controllers inheriting from this one that do not have controller specific formatters applied.
There are 2 attributes applied here - one to verify Angular's CSRF tokens and one to apply a controller specific formatter.
This means that all controllers inheriting from this will NOT have global formatters applied to it. Controllers inheriting from this controller are specifically made to work with Angular in the Umbraco back office, that is it. This is why you get that strange prefixed json on the response, this is because it applies a very specific Angular json formatter: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/WebApi/AngularJsonOnlyConfigurationAttribute.cs
So to answer your question, if you need to use UmbracoAuthorizedJsonController because you are creating angular based requests for the back office and you want to apply custom formatting output to that, then you need to apply a controller specific formatter to your controller. Or you can attribute your classes/properties with the correct DataContract bindings to specify the exact names you want.
If it's for back office use then you absolutely must continue to use the AngularJsonMediaTypeFormatter - you can create a sub-class of this to modify it and apply the CamelCasePropertyNamesContractResolver to it
using DataContract attribute is as mention. Please have a browse through our source code, there's hundreds of examples of all of this stuff in there.
Okay, I might take a look at it at some other time. For now I found it easiest just to add [DataContract] and [DataMember] attributes for the model class and properties.
using System;
using System.Runtime.Serialization;
namespace Reviews.Models
{
[DataContract]
public class Review
{
[DataMember(Name = "id", EmitDefaultValue = false)]
public int Id { get; set; }
[DataMember(Name = "name", EmitDefaultValue = false)]
public string Name { get; set; }
[DataMember(Name = "city", EmitDefaultValue = false)]
public string City { get; set; }
[DataMember(Name = "date", EmitDefaultValue = false)]
public DateTime Date { get; set; }
[DataMember(Name = "destination", EmitDefaultValue = false)]
public string Destination { get; set; }
[DataMember(Name = "header", EmitDefaultValue = false)]
public string Header { get; set; }
[DataMember(Name = "hotel", EmitDefaultValue = false)]
public string Hotel { get; set; }
[DataMember(Name = "hotelRating", EmitDefaultValue = false)]
public int HotelRating { get; set; }
[DataMember(Name = "rating", EmitDefaultValue = false)]
public int Rating { get; set; }
[DataMember(Name = "showComment", EmitDefaultValue = false)]
public bool ShowComment { get; set; }
[DataMember(Name = "text", EmitDefaultValue = false)]
public string Text { get; set; }
[DataMember(Name = "photo", EmitDefaultValue = false)]
public string Photo { get; set; }
}
[DataContract]
public class ReviewSearchResult
{
[DataMember(Name = "id", EmitDefaultValue = false)]
public int Id { get; set; }
[DataMember(Name = "name", EmitDefaultValue = false)]
public string Name { get; set; }
[DataMember(Name = "url", EmitDefaultValue = false)]
public string Url { get; set; }
[DataMember(Name = "path", EmitDefaultValue = false)]
public string Path { get; set; }
[DataMember(Name = "review", EmitDefaultValue = false)]
public Review Review { get; set; }
}
}
or using [JsonProperty] in Newtonsoft.Json
using Newtonsoft.Json;
using System;
namespace Reviews.Models
{
public class Review
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("city")]
public string City { get; set; }
[JsonProperty("date")]
public DateTime Date { get; set; }
[JsonProperty("destination")]
public string Destination { get; set; }
[JsonProperty("header")]
public string Header { get; set; }
[JsonProperty("hotel")]
public string Hotel { get; set; }
[JsonProperty("hotelRating")]
public int HotelRating { get; set; }
[JsonProperty("rating")]
public int Rating { get; set; }
[JsonProperty("showComment")]
public bool ShowComment { get; set; }
[JsonProperty("text")]
public string Text { get; set; }
[JsonProperty("photo")]
public string Photo { get; set; }
}
public class ReviewSearchResult
{
[JsonProperty("id")]
public int Id { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
[JsonProperty("path")]
public string Path { get; set; }
[JsonProperty("review")]
public Review Review { get; set; }
}
}
We don't apply global filters that will affect your own controllers, Umbraco doesn't get in your way. We apply the filters we need in our own controllers at our controller level.
Here's the code in case anyone is wondering how to create the attribute to return the data in camel case
public class AngularJsonMediaFormatterCamelCase : AngularJsonMediaTypeFormatter
{
public AngularJsonMediaFormatterCamelCase()
{
SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
}
}
public class AngularJsonOnlyConfigurationCamelCaseAttribute : AngularJsonOnlyConfigurationAttribute
{
public override void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
var toRemove = controllerSettings.Formatters.Where(t => (t is JsonMediaTypeFormatter) || (t is XmlMediaTypeFormatter)).ToList();
foreach (var r in toRemove)
{
controllerSettings.Formatters.Remove(r);
}
controllerSettings.Formatters.Add(new AngularJsonMediaFormatterCamelCase());
}
}
I just realized I had found the same, yet more high-level solution as this and posted it below. Didn't understand it until I'd re-read the thread, so deleted again. :P Anyways, interesting details to read here for anyone wondering how it looks under the hood.
Return camelCase property names from API controller
I am running Umbraco 7.4.1 and have a custom property editor to search reviews (listview nodes) and pick some of the result (similar to the MNTP, but not with the whole tree structure and more details for each "Review" node.
For that purpose I am using an API Controller to return data from the Examine index.
Model:
API Controller
When using UmbracoApiController or UmbracoAuthorizedApiController it returns camelCase property names, but with UmbracoAuthorizedJsonController it seems to ignore that part and returns the default casing of the property names.
How can I use UmbracoAuthorizedJsonController and still return camelCase property names?
/Bjarne
Hi Bjarne,
There's a few things you'll need to understand in order to know what is going on here. Firstly, there are global formatters and then there are controller specific formatters. By default global formatters will apply to ALL controllers in your solution - but if we allowed for developers to change the formatting of our data to whatever they want then everything would probably just break. Therefore for Umbraco's purposes - we apply controller specific formatters so we know exactly how the data will be formatted & cannot be tampered with so the back office doesn't break.
Here's what these controllers do:
UmbracoAuthorizedApiController
UmbracoAuthorizedJsonController
So to answer your question, if you need to use
UmbracoAuthorizedJsonController
because you are creating angular based requests for the back office and you want to apply custom formatting output to that, then you need to apply a controller specific formatter to your controller. Or you can attribute your classes/properties with the correct DataContract bindings to specify the exact names you want.Hi Shannon
Thanks for your detailed explanation of the difference between the two API Controllers.
I am not quite sure how to apply a controller specific formatter to a controller - do I need to create a formatter similar to this? https://github.com/umbraco/Umbraco-CMS/blob/d50e49ad37fd5ca7bad2fd6e8fc994f3408ae70c/src/Umbraco.Web/WebApi/AngularJsonMediaTypeFormatter.cs
By adding DataContract attributes for properties and classes, it should be something similar to this, right? https://msdn.microsoft.com/en-us/library/jj870778.aspx
If it's for back office use then you absolutely must continue to use the
AngularJsonMediaTypeFormatter
- you can create a sub-class of this to modify it and apply theCamelCasePropertyNamesContractResolver
to itusing DataContract attribute is as mention. Please have a browse through our source code, there's hundreds of examples of all of this stuff in there.
Hi Shannon
Okay, I might take a look at it at some other time. For now I found it easiest just to add
[DataContract]
and[DataMember]
attributes for the model class and properties.or using
[JsonProperty]
inNewtonsoft.Json
Thanks,
Bjarne
Hi Shannon,
Where in the source code I can check what global formatters are applied to all controllers?
Thanks
There really aren't any of relevance: https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/WebBootManager.cs#L235
We don't apply global filters that will affect your own controllers, Umbraco doesn't get in your way. We apply the filters we need in our own controllers at our controller level.
Here's the code in case anyone is wondering how to create the attribute to return the data in camel case
Cheers.
I just realized I had found the same, yet more high-level solution as this and posted it below. Didn't understand it until I'd re-read the thread, so deleted again. :P Anyways, interesting details to read here for anyone wondering how it looks under the hood.
https://blogs.msdn.microsoft.com/jmstall/2012/05/11/per-controller-configuration-in-webapi/
I blogged about this a while back too :)
http://shazwazza.com/post/webapi-per-controller-configuration/
is working on a reply...