Razor vs. "/base" to output Json in Umbraco

Note: this post is over a year old, it's very likely completely outdated and should probably not be used as reference any more. You have been warned. :-)

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!

2011-07-25_104816

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:

2011-07-25_102146

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).

Sebastiaan Janssen

Dutch guy living in (and loving) Copenhagen, working at Umbraco HQ. Lifehacker, skeptic, music lover, cyclist, developer.

 

29 comments on this article

Avatar for HJAbbing HJAbbing | July 25 2011 11:00
Awesome and very usefull post. I was wondering what you meant with that tweet!

Avatar for HJAbbing HJAbbing | July 25 2011 13:51
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.

Avatar for Sebastiaan Janssen Sebastiaan Janssen | July 25 2011 14:38
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)

Avatar for HJAbbing HJAbbing | July 25 2011 14:55
Excellent, that fixed it. Thanx!

Avatar for Jason Prothero Jason Prothero | July 26 2011 07:32
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.

Grat post and thanks for the tip!

Avatar for Simon Justesen Simon Justesen | August 2 2011 13:55
Just a quick tip - you don't have to specify ?alttemplate= anymore (you can, but...), it is the old syntax .. imho It's much nicer to write yourpage.aspx/your_template_alias e.g. yourpage.aspx/jsonoutput <-- nice, eh? :)

Have a nice day :)
Simon

Avatar for Simon Justesen Simon Justesen | August 2 2011 13:56
Hey, almost forgot.. Nice article! :)

Avatar for Sebastiaan Janssen Sebastiaan Janssen | August 2 2011 13:58
Great tip Simon, thanks!

I did realize later that I could do that. Then again, if it's not a url that you need to link to then it's not much of a problem to have the querystring in there.

Avatar for Simon Justesen Simon Justesen | August 2 2011 14:01
Duh, I see that you're using the new syntax further above .. sorry.. *note to self: do not just skim the text* ;)

Avatar for Simon Justesen Simon Justesen | August 2 2011 14:03
You're right :) It was just for aestetics.. A designer thing.. *lol*

Avatar for Eran Eran | August 9 2011 11:12
really interesting. thanks.

Avatar for bob baty-barr bob baty-barr | September 21 2011 23:49
Sebastiaan.... stuff like this is just awesome! but of course, i have a question ;)

if 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's say :)

where would i start?

Avatar for bob baty-barr bob baty-barr | September 22 2011 00:09
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 :(

Avatar for Sebastiaan Janssen Sebastiaan Janssen | September 22 2011 13:53
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));
}

Avatar for bob baty-barr bob baty-barr | September 22 2011 15:25
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.

Avatar for bob baty-barr bob baty-barr | September 22 2011 15:28
well, color me learning :)
i just needed to cast it with .ToString()

hooray for me!
and many thanks to you Sebastiaan, my friend!

Avatar for Sebastiaan Janssen Sebastiaan Janssen | September 22 2011 15:28
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.

Avatar for Sebastiaan Janssen Sebastiaan Janssen | September 22 2011 15:29
Haha, ah yes, that works as well, thanks for the reminder. ;-)

Avatar for bob baty-barr bob baty-barr | September 22 2011 16:44
grrr.... but what if i want all the properties of the node in Json, not just the property...

i wish i was a programmer :(

Avatar for Sebastiaan Janssen Sebastiaan Janssen | September 22 2011 16:47
Your first attempt did just that, all of the properties were listed as you were for-eaching through the children..

Avatar for bob baty-barr bob baty-barr | September 22 2011 17:02
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 :(

Avatar for Sebastiaan Janssen Sebastiaan Janssen | September 22 2011 17:16
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));
}
}

Avatar for shears shears | October 13 2011 10:37
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.

Avatar for Omid Tansaz Omid Tansaz | July 11 2012 13:04
JSON with children also containing node ID & Names:



@{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var myList = new List();
foreach (var item in Model.Children.Where("umbracoNaviHide != true"))
{
var thisNode = new umbraco.NodeFactory.Node(item.Id);
var thisNodeProperties = new Dictionary();
thisNodeProperties.Add("Id",thisNode.Id); thisNodeProperties.Add("Name",thisNode.Name);
thisNodeProperties.Add("Properties",thisNode.PropertiesAsList);
myList.Add(thisNodeProperties);
}
@Html.Raw(serializer.Serialize(myList));
}

Avatar for Leandro Leandro | November 21 2012 12:06
This approach is great, but it has performance problems on large scale websites, I gues it's 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.

Avatar for Omid Tansaz Omid Tansaz | November 21 2012 13:07
@leonard

what do u mean with a node that uses the template? So do you suggest that we shouldn't use altTemplate method?

Avatar for Leandro Leandro | November 21 2012 13:20
@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.

Avatar for John Walker John Walker | April 22 2013 19:24
Just been looking at doing this myself, like GFoley83 approach to having the properties easier to access, code used:

@inherits Umbraco.Web.Mvc.UmbracoTemplatePage

@{
var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();

var propertiesDict = new Dictionary();
Model.Content.Properties.ForEach(x => propertiesDict.Add(x.Alias, x.Value));
}

@Html.Raw(serializer.Serialize(propertiesDict))

Avatar for GFoley83 GFoley83 | December 10 2012 05:39
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