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
HJAbbing | July 25 2011 11:00
Awesome and very usefull post. I was wondering what you meant with that tweet!
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.
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)
HJAbbing | July 25 2011 14:55
Excellent, that fixed it. Thanx!
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!
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
Simon Justesen | August 2 2011 13:56
Hey, almost forgot.. Nice article! :)
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.
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* ;)
Simon Justesen | August 2 2011 14:03
You're right :) It was just for aestetics.. A designer thing.. *lol*
Eran | August 9 2011 11:12
really interesting. thanks.
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?
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 :(
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));
}
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.
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!
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.
Sebastiaan Janssen | September 22 2011 15:29
Haha, ah yes, that works as well, thanks for the reminder. ;-)
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 :(
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..
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 :(
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));
}
}
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.
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
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.
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?
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.
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))
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