Razor vs. "/base" to output Json in Umbraco
A few months ago I posted about outputting javascript objects (Json) using an umbraco's "/base". As you might've noticed, I've been becoming a fan of using Razor in Umbraco and had a bit of an epiphany the other day: why not use Razor to create my Json strings!
So, to do this, I created a new template, let's call it "RazorJson" and add a new macro on it:
<umbraco:Macro FileLocation="~/macroScripts/give-me-json.cshtml" runat="server" />
It's important to remember that, in Umbraco, you don't have to create a new page that actually uses this template, if there's no URL conflicts (a node in the root of the site is not called RazorJson), then you can call a template directly. Do note that that context in this case will be the home page (more on that later).
By going to http://mysite.com/RazorJson I get to see my newly created template, but because it is still empty at the moment I get a blank screen.
Let's say that I want to output the current node's properties in Json, we can just do something very simple like this:
@{ var node = new umbraco.NodeFactory.Node(Model.Id); var serializer = new System.Web.Script.Serialization.JavaScriptSerializer(); }
@Html.Raw(serializer.Serialize(node.Properties))
So we're taking the current node's Id (Model.Id) and instantiate a new Node() from it. Then it's properties gets serialized into a Json string and that is what's being shown on the page.
Presto!
Now, to make it a bit more interesting, remember how I said this page would only run in the home page's context? You could just turn any of your pages into a Json object by going to, for example, http://mysite.com/about?altTemplate=RazorJson
This will render the "about" page with the RazorJson template and therefore output all of the "about" page's properties in Json notation.
I tweeted about this and got some comments, among which this one:
Gareth is right, to create a /base extension, you write a seperate class that handles your businesslogic for you and /base just outputs either XML or a string.
However, I think using a template with a Razor script on it seems a bit more powerful. As I've shown above, it's easy to work within the context of a page using altTemplates, but you can also still write any old class that returns a string (or XML) and call that from Razor. In fact, I could just call my existing /base extension from the Razor file because, as you should know, from Razor you can access any old class in your solution.
What I do see happening is that people will write @functions in Razor and never extract them into a seperate method. This is great for quick prototyping and if you really are never going to use those functions again, it's no problem. But it does make it a bit easier to start ignoring seperation of concerns and the DRY (don't repeat yourself) rule and just start copying functions from one Razor script into the other.
All I can say is: with great power comes great responsibility. If you want to be able to change your code in few months, make sure to refactor early and refactor often (#47).
29 comments on this article
Awesome and very usefull post. I was wondering what you meant with that tweet!
One question. I am having some trouble parsing the Json..
$.getJSON(url, { alttemplate: "RazorJson" }, function(data) {
console.log("success");
});
Returned data is in this format: [{"Alias":"thema",...
Callback doen't fire. Is this because of invalid Json? Setting the content-type header to Json doesn't help.
While the output on the page looks fine and (according to jsonlint.com) is in fact valid Json, there is indeed a problem.
Razor does automatic HTML encoding on it's output. This causes the output to actually look like this:
[{"quot;Alias"quot;:"quot;testBoolean"quot;,"quot;Value... etc.
You can fix it by changing the serialize line to output the raw "HTML":
@Html.Raw(serializer.Serialize(node.Properties))
(thanks, I updated the post as well)
Excellent, that fixed it. Thanx!
I like using alt templates for my HTML Ajax needs. And I like the idea of a quick way to produce Json when needed for simple things as well.\u003Cbr /\u003E\u003Cbr /\u003EGrat post and thanks for the tip!
Just a quick tip - you don\u0027t have to specify ?alttemplate= anymore (you can, but...), it is the old syntax .. imho It\u0027s much nicer to write yourpage.aspx/your_template_alias e.g. yourpage.aspx/jsonoutput \u003C-- nice, eh? :)\u003Cbr /\u003E\u003Cbr /\u003EHave a nice day :)\u003Cbr /\u003ESimon
Hey, almost forgot.. Nice article! :)
Great tip Simon, thanks!\u003Cbr /\u003E\u003Cbr /\u003EI did realize later that I could do that. Then again, if it\u0027s not a url that you need to link to then it\u0027s not much of a problem to have the querystring in there.
Duh, I see that you\u0027re using the new syntax further above .. sorry.. *note to self: do not just skim the text* ;)
You\u0027re right :) It was just for aestetics.. A designer thing.. *lol*
really interesting. thanks.
Sebastiaan.... stuff like this is just awesome! but of course, i have a question ;)\u003Cbr /\u003E\u003Cbr /\u003Eif i am wanting to do something very specific, like json serialize subnodes and specific properties of those nodes to help populate a dropdown via jquery let\u0027s say :)\u003Cbr /\u003E\u003Cbr /\u003Ewhere would i start?
i know this is not right...
@foreach (var item in @Model.Children.Where("umbracoNaviHide != true")){
var node = new umbraco.NodeFactory.Node(item.Id);
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
@Html.Raw(serializer.Serialize(node.Properties));
}
but i don't really know how to scope the vars right :(
Hey Bob, you got it almost right, just move the serializer outside of the foreach.
Anyway, if you only need to have a list of a single property, you could easily just.. put them in a list! Like so:
@{
var propList = new List
foreach (var item in Model.Children.Where("umbracoNaviHide != true"))
{
propList.Add(item.MyProperty);
}
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
@Html.Raw(serializer.Serialize(propList));
}
razor ignorance here...
@{
var propList = new List
foreach (var item in Model.Children.Where("umbracoNaviHide != true"))
{
propList.Add(item.annualPrice);
}
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
@Html.Raw(serializer.Serialize(propList));
}
produced the following error...
The best overloaded method match for 'System.Collections.Generic.List.Add(string)' has some invalid arguments
what did i do wrong?
annualPrice is the alias of the property i want to list in Json.
well, color me learning :)\u003Cbr /\u003Ei just needed to cast it with .ToString()\u003Cbr /\u003E\u003Cbr /\u003Ehooray for me!\u003Cbr /\u003Eand many thanks to you Sebastiaan, my friend!
Hehe, maybe this would be better as a forum post by the way, but I think we're nearly there, try this:
Instead of item.annualPrice (it's probably parsed as an int), do item.GetPropertyValue("annualPrice"). If you still get that error, also test if(item.GetPropertyValue("annualPrice") != string.Empty) before you do the .Add.
Haha, ah yes, that works as well, thanks for the reminder. ;-)
grrr.... but what if i want all the properties of the node in Json, not just the property...\u003Cbr /\u003E\u003Cbr /\u003Ei wish i was a programmer :(
Your first attempt did just that, all of the properties were listed as you were for-eaching through the children..
when i do this...
@{
foreach (var item in @Model.Children.Where("umbracoNaviHide != true")){
var node = new umbraco.NodeFactory.Node(item.Id);
}
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
@Html.Raw(serializer.Serialize(node.Properties));
}
i get an error about the name node does not exist in current context :(
Other way around, the scoping is in the curlies as you will notice:
@{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
foreach (var item in Model.Children.Where("umbracoNaviHide != true"))
{
var node = new umbraco.NodeFactory.Node(item.Id);
@Html.Raw(serializer.Serialize(node.Properties));
}
}
Thank you for the nice blog. It was very useful for me. Keep sharing these ideas in the future too. It was actually what I wanted and I am happy to be here! Thank you for sharing this information with us.
JSON with children also containing node ID & Names:
@{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var myList = new List
This approach is great, but it has performance problems on large scale websites, I gues it\u0027s related with routing system. The problem can be solved by adding a node that uses the template (with the same name), you will notice big difference in response time.
@leonard\u003Cbr /\u003E\u003Cbr /\u003Ewhat do u mean with a node that uses the template? So do you suggest that we shouldn\u0027t use altTemplate method?\u003Cbr /\u003E\u003Cbr /\u003E
@Omid
With "http://mysite.com/RazorJson" pointing to a template I found poor performance in sites with hundreds of nodes. An easy and transparent workarround (if you already implemented this on a live site) is to create a node (in this case in the root) called "RazorJson" that uses the "RazorJson" template. You will see the difference in response time.
Just been looking at doing this myself, like GFoley83 approach to having the properties easier to access, code used:\u003Cbr /\u003E\u003Cbr /\u003E@inherits Umbraco.Web.Mvc.UmbracoTemplatePage\u003Cbr /\u003E\u003Cbr /\u003E@{\u003Cbr /\u003E var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();\u003Cbr /\u003E\u003Cbr /\u003E var propertiesDict = new Dictionary\u003Cstring, object\u003E();\u003Cbr /\u003E Model.Content.Properties.ForEach(x =\u003E propertiesDict.Add(x.Alias, x.Value));\u003Cbr /\u003E}\u003Cbr /\u003E\u003Cbr /\[email protected](serializer.Serialize(propertiesDict))
I find that serializing "node.Properties" directly to json isn't very helpful because to retrieve the values, you either have to search the json array for the property you want or know the index of your property beforehand
E.g
[0].Value, [1].Value etc
I use the following instead:
@using umbraco.NodeFactory
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
var node = new umbraco.NodeFactory.Node(Model.Content.Id);
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var propDic = new Dictionary
foreach (Property prop in node.Properties)
{
propDic.Add(prop.Alias, prop);
}
}
@Html.Raw(serializer.Serialize(propDic))
This was I can query my json object a little easier,
E.g.
myPropertyAlias.Value