How To Create a Simple Ajax CMS System with ASP.NET Ajax
So my most recent client needs a CMS system. There are a million out there, but the problem is my client is a complete Microsoft shop so I had to write the page in ASP.NET. That changes the game a lot. Unless you want to pay some serious cash, the choices for a CMS system are slim.
That isn’t to say there aren’t some free ones out there because there are. I uncovered two during my search (AxCMS and umbraco). I didn’t delve very far into either of these because it was shortly my client and I discussed the CMS system that we decided to make the page have a side scrolling page effect.
When you create a page that has a slick effect you basically throw out any packaged software because inevitably it won’t fit in with the effect. So that is how I came to build my own CMS system. Now I use that term lightly because compared to something like Wordpress, mine is rather weak.
However, it does what it was designed to do…save pages. It could easily be enhanced to allow things such as creating completely new pages, deleting pages, etc, but my client didn’t need that functionality so I didn’t build it for this version, but perhaps I will in the future.
So the point of this article is to go through the steps I took to make it work (here is the final result).
It turned out to be a lot more difficult than I originally anticipated. Part of that was just the learning curve, and part of it was wrestling ASP.NET Ajax to the ground. In the end though, I am pretty proud of my first version of my CMS.
What is a CMS anyway?
For those of you who have never encountered this term before, a CMS is a “Content Management System”. There are tons of them out there…wordpress, light, and drupal are a few of the more well known ones that are linux based.
The point of a CMS package is to publish content…this could be anything from simply allowing your clients to update the static pages on their site to a blog to a full intranet site. A true CMS will have a lot of features, but when it comes to “day to day” use, most people don’t really use them all that much.
All of the ones I mentioned are great, and if you can use one that is already developed, then I recommend doing so. However, there are cases where a package deal just won’t fit. This could be because you have specific design requirements or your clients simply want you to build one from scratch. Whatever the reason, I hope this article helps you get on your way.
Demo
Before we get building, its always helpful to see what the end result looks like. In an effort to keep the demo as simple as possible there are no database connections in the demo, but it is loading its data from the server side. I have also stubbed out the save process. So with that…lets get started.
TinyMCE
If you really wanted to you could build a CMS system that is just a big textbox with a save button next to it (and yes, several years ago I built just that). However, we are in the post-web 2.0 world and that just isn’t going to cut it. We need a rich text editor, and we need it to work without all those annoying flickers from postbacks.
You could build your own rich text editor…but why? TinyMCE is slicker than a wet oyster mainly because its fully functional, javascript based, easy to use, and completely free. Plus there is a lot of great documentation to boot. So with that said, my little CMS uses it to do all of the heavy lifting (well most of it anyway).
The basics of TinyMCE is a million javascript files and 2 lines of actual javascript. Now I am not going to go into all the various functions of this amazing doodad (installation and how to use it are well documented), but here is how easy it can be to implement.
<script language="javascript" type="text/javascript"
src="../jscripts/tiny_mce/tiny_mce.js"></script>
<script language="javascript" type="text/javascript">
tinyMCE.init({
mode : "textareas"
});
</script>
In the demo (and the sample code I link to at the end of the article), I am using a lot of the more “advanced features”. If you want to see what all the various features are check out the examples page.
Markup!
So, like all webpages, we need some markup. Pretty simple stuff overall, but there are a couple of points we should take note of.
<form id="form1" runat="server">
<asp:ScriptManager ID="smgr" runat="server" EnablePageMethods="true" />
<asp:UpdatePanel runat="server" ID="updatePanel">
<ContentTemplate>
<div id="page">
<div id="ContentBox">
<div id="Content">
Please select the page you wish to edit. When you are finished,
click the "Save" button and your updates
will be visible immediately on the site.<br/><br/>
<div class="pages">
Pages:
<select runat="server" id="ddlPages" onchange="ajaxLoad();">
<option value="0">Page 1</option>
<option value="1">Page 2</option>
<option value="2">Page 3</option>
</select>
</div>
<textarea name="content" runat="server"
id="content" cols="10" rows="10" class="tinyMCE"></textarea>
<div id="lblSaveResult" class="SaveStatus"></div>
<div id="Save">
<input type="button" id="btnSave" class="button" value="Save"
onclick="ajaxSave();" />
</div>
</div>
</div>
</div>
</ContentTemplate>
</asp:UpdatePanel>
</form>
So there it is…please keep the applause to a minimum during the demostration. The CSS used in the demo is nothing special or fancy. It just is there to make the page better looking than having no style at all. For this reason, I will skip it and move directly to the HTML.
The first thing that is a bit different is our ScriptManager. Now, this guy is required for all ASP.NET Ajax pages so we are used to seeing him. However, this time you will notice the property EnablePageMethods is set to “true”. EnablePageMethods is what allows us to call server side methods from Javascript. Its a pretty neat trick, and once you get the hang of it, you can begin to see the potential.
Next we have our UpdatePanel, a few divs, and then a select. Now the question is…why in an ASP.NET page am I using an old school select rather than an asp:DropDownList? Basically it boils down to I need this client side, not server side. The ASP.NET controls are great if you are manipulating things from the backend, but when the control is only meant to be used by the client, it is often easier to go the old school way. Now I have this set to runat=”server”, but it really isn’t necessary in this simple of a demo.
On my select I have my first javascript method call (ajaxLoad()). I will get to the actual method in a bit, but for now just note that I have it tied to the onchange event so that I can load the content from the server whenever someone selects a different page.
The next important piece is the textarea. This is where TinyMCE does its magic. I have a Css class here called “TinyMCE”, but that is my doing and is not required for the tool to work. The class is only there to make my text area the size I want it to be.
Finally there is the Save button. Again, since I am calling all of my server methods from javascript, I do not need to use an ASP.NET server control. Also notice that I have another javascript method call here (ajaxSave()) so I can kick off our save process when it is clicked.
Javascript
Now that we understand the markup, lets see how this thing actually does what it does…enter javascript.
function ajaxLoad()
{
var ddl = document.getElementById('ddlPages');
var id = ddl.options[ddl.selectedIndex].value;
PageMethods.LoadMarkup(id, LoadCallback);
}
function ajaxSave() {
var inst = tinyMCE.getInstanceById('content');
var ddl = document.getElementById('ddlPages');
var value = ddl.options[ddl.selectedIndex].value + "|" + inst.getHTML();
PageMethods.SaveMarkup(value, SaveCallback);
}
// This is the callback function that
// processes the Web Service return value.
function LoadCallback(result)
{
var inst = tinyMCE.getInstanceById('content');
inst.setHTML(result);
}
function SaveCallback(result)
{
var lbl = document.getElementById('lblSaveResult');
lbl.innerHTML = "Save Successful";
}
So we have 4 methods…two are called from the client and two that are called by the server. Lets start with the client methods.
ajaxLoad() is called whenever we change the page on our little select. His job is simple…find out what page is selected, and tell the server. Now we tell the server using some slightly fancy ASP.NET code called PageMethods. If a method is setup as a WebMethod (I will show how to do that a bit later), then it can be called from the client. In this method I call a WebMethod called LoadMarkup and I send it back the ID of the page.
Note - there can only be 2 parameters. The first can be just about anything that fits into a string. The second is the client method name that the server will call once the asynchronous call is complete. In my case, I named it LoadCallback.
The next client side method is ajaxSave(). As you probably have guessed, the purpose of this method is to tell the server that we need to save something. To do that we go through a couple of hurdles.
- First I create an object that represents our text area. I do this by calling on the TinyMCE API to give me what I need.
- The next thing that needs to be done is I need to build up what I am sending back to the server. Remember that I can only send back 1 custom parameter to the server. The problem is I need to send back two pieces of information. Since I only have one place to put both pieces I concatenate the two pieces of info and separate them by a “|” so that I can split them on the backend. Also check out the inst.getHTML()…this is TinyMCE’s method that reads what is in the text area and makes it valid HTML.
- Once I have my info loaded, I make another call to a WebMethod called SaveMarkup.
Now lets take a look at the two server methods. Both of these guys exist simply to tell the client that we have successfully done whatever we wanted to do on the server. Both methods have a single parameter called “result” that is loaded by the server and contains whatever we needed to return.
LoadCallback() is invoked when we have successfully loaded the markup for the selected page. In this method I create another TinyMCE object and set the result (which contains the markup that I saved earlier).
SaveCallback() has a result of null because in the demo I am not returning anything. I simply let the user know that everything worked out ok.
Server Side
Ok, so we now know how it all works from the client side…what’s going on on the backend?
protected void Page_Load(object sender, EventArgs e)
{
//normally this would be where you would get the markup for your first page from the
//database, but here we just hardcode it.
content.Value = "Page 1 Markup";
}
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public static string LoadMarkup(int value)
{
//again, this should really come from the database, but that would add too much
//complexity to the demo.
switch (value)
{
case 0:
return "Page 1 Markup";
case 1:
return "Page 2 Markup";
case 2:
return "Page 3 Markup";
}
return null;
}
[System.Web.Services.WebMethod]
[System.Web.Script.Services.ScriptMethod]
public static string SaveMarkup(string value)
{
//the value is stored id|markup..gotta split that out
string[] valueArray = value.Split("|".ToCharArray());
//This is where you would save to your database...
return null;
}
So here you see 3 different methods. Page_Load is required to be there. In it I am simply initializing the text area with the first page’s markup. As I mention in the comments, this should come from the database, but I am just hardcoding it for simplicity.
Next we come to the real meat and potatoes…the server side methods that are called from the client. To enable this handy feature a couple of requirements must be met.
- The method must be static. This essentially means that the method can be called without instantiating the class. The problem with this is that static methods lose a lot of the stuff developers (including myself) like to use on the backend such as Response, Request, and accessing any of your server controls. In other words, static methods on a web page are pretty basic.
- The method must be a web method. This is accomplished by having the following two attributes: [System.Web.Services.WebMethod] and [System.Web.Script.Services.ScriptMethod].
LoadMarkup() as mentioned before loads up the markup for the various pages to be loaded by the client. In the demo this is all hardcoded, but it is pretty easy to get it to run from a database.
SaveMarkup has a single task…save the given markup. The problem is, the markup has been concatenated with the id of the page we want to save. In order to get both pieces we have to split them up into a string array. No biggie, but I sure wish I could send multiple parameters rather than go this hacktastic route. Notice in my demo that the save doesn’t really do anything. This, again, was so I didn’t have to worry about a database.
Whew…
Ok, so there it is…the first phase of my Simple CMS. Its not sexy, but what makes up for it is the fact that it can fit in literally any design because, unlike the big boys, this doesn’t take over your site. It merely adds to it. I have enjoyed working with it so I hope to, at some point, make it more robust and flexible so that it could be used by just about anyone.
Before I began this I did not know anything about PageMethods so it has been a real learning experience for me. I hope you find it as useful. If you want to download the demo, feel free. I have included the latest version of TinyMCE in the zip file so you can also play with that as well.
If you get a moment, go check it out.


Hartvig said,
Wrote on August 7, 2007 @ 3:17 am
The effect you want could very easily be re-created with umbraco as it neither effects your markup - you’re in complete control. So you can use the same code as you originally wanted without any need for a change. umbraco is different from “the rest” as it see you as the king instead of the system as a king. So you shouldn’t integrate to the system - the system shall integrate to you
How much time did you spend on creating your own cms - I’d be happy to take the challenge to completely re-produce your effect with umbraco without any need to modify the umbraco core
Cheers,
Niels / umbraco
Jesper Ordrup said,
Wrote on August 7, 2007 @ 5:01 am
Nice effect … but you didn’t do your research and made a wrong turn. Umbraco lets you do what ever you like. Usercontrols, xslt and your own markup. Completly.
Kindly,
Jesper
David Baxter said,
Wrote on August 7, 2007 @ 9:20 am
Hey Niels and Jesper, thanks for the comments. As I mentioned, I did not delve deeply into Umbraco, however out of the two free .NET CMS apps, I did find Umbraco to be the easier of the two.
I may have jumped the gun by building my own, but the experience taught me a lot and in turn I was able to share that newfound knowledge with the readers here. So all in all it was a worthwhile endeavor.
How long did this take me? Well, if I would have known how to use PageMethods beforehand I would have saved a lot of time (at least an hour). As it is though, it took me about 3 hours to get the initial work complete.
Niels, if you wish to reproduce the effect with Umbraco, then I would love to see it. I am sure that the readers here would love to see that. Shoot me an email and we can get the “challenge” setup correctly.
David
Jon said,
Wrote on August 8, 2007 @ 4:41 pm
For what its worth, ASP:DropDownList controls render out as a Select box, and you can address and manipulate them on the clientside by passing a ClientID attribute to the control.
David Baxter said,
Wrote on August 8, 2007 @ 4:44 pm
Yeah, I have noticed that, but if you are never going to use it on the server side, why not do it the easy way?
There isn’t a right or wrong on this…just personal preference.
David
P.S. I like your site by the way…geeksnotnerds.com…great name.
ahmad said,
Wrote on August 12, 2007 @ 10:36 am
i removed the update panel and every thing working fine. why you use it.
David Baxter said,
Wrote on August 12, 2007 @ 4:56 pm
Hey Ahmad, glad you fixed it…but what was the problem?
The reason for the UpdatePanel is to give the CMS an “ajax feel”. Without it, the page will work, but you will get the flickering from a full page reload.
I have used the CMS a couple of times (and I am actually expanding it and hoping to turn it into its own little project) and haven’t had any problems with the UpdatePanel.
David
Prithvi said,
Wrote on January 30, 2008 @ 3:20 am
gr8… work