Code, Code, Revolution!
This is the first part in a planned series of explaining techniques and examples of how you can create more advanced EPiServer properties by utilizing Ajax.
In this first part we’re going “old skool” by implementing Ajax callbacks using ICallbackEventHandler (msdn). ICallbackEventHandler first showed up in .NET Framework 2.0 and when EPiServer 4 introduced .NET 2.0 support it was the easiest way to create an easy to distribute ajax enabled function because it has no dependencies on additional packages, such as the ASP.NET AJAX Extensions 1.0. The ICallbackEventHandler method is still viable today with .NET Framework 3.5 for simpler ajax implementations. In fact, the DataContractJsonSerializer makes it even less painful.
In this post I’ve built a property that might not be very useful for anything other than a proof-of-concept. It’s a page selector property with ajax enabled search functionality, the editor can type a string into a box and the property will search for pages (using FindAllPagesWithCriteria) containing that string in its name. The result is presented in a combo box and the editor can then pick a page from there. Here’s what it looks like:

It might not be the most visually appealing property but that’s not really our goal here.
The ICallbackEventHandler interface requires only two methods to be implemented:
string GetCallbackResult(); void RaiseCallbackEvent(string eventArgument);
The concept is very simple, your javascript callback goes to RaiseCallbackEvent containing a string with argument(s) and the result is then returned through GetCallbackResult as a string. With .NET 3.5 you can easily serialize any (almost) class into JSON using DataContractJsonSerializer which is really a huge advantage from previous frameworks. Before you had to either use an additional JSON library, manipulate your own strings into JSON or return a separated string that you need to parse in your javascript code.
First things first. We need to wire up the call-back from javascript to server. In OnLoad I register the javascript parts of my property. First i register my javascript file containing the client script parts of my property, more information on embedding javascript into a dll. Page.ClientScript.GetCallbackEventReference returns the reference to our server method. Note that we’re specifying that we want the reference to call our own javascript method ICallbackJsonPropertyControl_CallBack to handle the response from the server. I’m also wrapping the callback reference in my own javascript method “ICallbackJsonPropertyControl_FindPages”.
Note that the entire control can only have a single method (reference), and that method takes a single string argument. To overcome the limitations I’ve decided to use the “|” character as delimiter for my arguments in this project.
protected override void OnLoad(EventArgs e) { base.OnLoad(e); if (Page.ClientScript.IsClientScriptIncludeRegistered(this.GetType(), "ICallbackJsonPropertyControl") == false) { // Register our javascriptfile. The js-file is compiled into the ddl as a reasource for easy // distribution. Read more about resources here: http://blog.sallarp.com/howto-embed-javascript-dll-getwebresourceurl Page.ClientScript.RegisterClientScriptInclude("ICallbackJsonPropertyControl", Page.ClientScript.GetWebResourceUrl(this.GetType(), "MightyLittle.CMSR2.Properties.ICallbackJsonProperty.js")); // Time to register the callback for the ICallbackEventHandler. string callBackReference = Page.ClientScript.GetCallbackEventReference(this, "arg", "ICallbackJsonPropertyControl_CallBack", "", true); // Create a javascript method that will call the callback method generated above. string findPagesScript = @" function ICallbackJsonPropertyControl_FindPages(inputCtrlId, resultCtrlId) { var arg = document.getElementById(inputCtrlId).value + '|' + resultCtrlId; " + callBackReference + @"; }"; // Add our script as a script block on the page Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "ICallbackJsonPropertyControl_FindPages", findPagesScript, true); } }
In CreateEditControls() the text box which holds the property value is hidden using css and we’re instead using javascript to set the value of that text box when a page is selected in the combo box. We also wire the button to call our ICallbackJsonPropertyControl_FindPages method instead of triggering a post-back of the entire page.
public override void CreateEditControls() { if (base.RenderType == EPiServer.Core.RenderType.Edit) { // Create a listbox to present the pages ListBox lbResults = new ListBox(); lbResults.ID = "pages"; lbResults.SelectionMode = ListSelectionMode.Single; // Create a box for the search function TextBox searchBox = new TextBox(); searchBox.ID = "searchBox"; // The button that will perform the search Button btnFindPages = new Button(); btnFindPages.Text = "Find pages"; // This is the box that will contain the property value on postback // we hide it from the user using css mTextBox.ID = "resultbox"; mTextBox.Attributes.Add("style", "display:none;"); HtmlGenericControl br = new HtmlGenericControl("br"); HtmlGenericControl br2 = new HtmlGenericControl("br"); // Add the controls this.Controls.Add(lbResults); this.Controls.Add(br); this.Controls.Add(searchBox); this.Controls.Add(mTextBox); this.Controls.Add(br2); this.Controls.Add(btnFindPages); // Wire the button to call our javascriptmethod for finding pages btnFindPages.OnClientClick = string.Format("ICallbackJsonPropertyControl_FindPages('{0}','{1}'); return false;", searchBox.ClientID, lbResults.ClientID); // Wire the listbox to set the selected value into our property textbox that's hidden. lbResults.Attributes.Add("onchange", string.Format("ICallbackJsonPropertyControl_PageSelected(this,'{0}');", mTextBox.ClientID)); } }
I’ve implemented a simple FindAllPagesWithCriteria search in RaiseCallbackEvent. The result is made up by two classes: FoundPagesAjaxResponse which contains a list of TinyPageInfo objects. The entire result object is then serialized into JSON and stored in the variable _callbackResult which is the variable we return as the result of the ajax call in GetCallbackResult().
// String to hold the result of the ajax call private string _callbackResult = string.Empty; /// /// This method is called upon the ajax request. This is where we search for pages /// void System.Web.UI.ICallbackEventHandler.RaiseCallbackEvent(string eventArgument) { // The method only takes one parameter and sometimes that's not // enough. So we use a separator to pass more than one argument // in a single string. This time we're using the |-sign if (!string.IsNullOrEmpty(eventArgument)) { // Parse the arguments. In this case we will get the search term // first and the second will be the ClientID of the listbox that // we want to render the results in string[] arguments = eventArgument.Split('|'); // Perform a simple search for pages PropertyCriteriaCollection criterias = new PropertyCriteriaCollection(); PropertyCriteria crit = new PropertyCriteria(); crit.Condition = EPiServer.Filters.CompareCondition.Contained; crit.Type = PropertyDataType.String; crit.Name = "PageName"; crit.Value = arguments[0]; criterias.Add(crit); PageDataCollection pages = EPiServer.DataFactory.Instance.FindAllPagesWithCriteria(PageReference.StartPage, criterias, LanguageBranch.LoadFirstEnabledBranch().URLSegment ,LanguageSelector.MasterLanguage()); // Create a new response object. We set the resultlistid property to the id // of the listbox that was passed to this method as an argument. That way // our javascript will know where to render the results. FoundPagesAjaxResponse response = new FoundPagesAjaxResponse() { ResultListId = arguments[1] }; foreach (PageData page in pages) { // Add information about the page to our response collection. response.Pages.Add(new TinyPageInfo() { PageName = page.PageName, PageID = page.PageLink.ID }); } // Return our result as json. _callbackResult = response.ToJSON(); } }
Here are the result classes that we serialize into JSON
/// /// The class must be decorated with DataContract to /// be serialized into Json /// [DataContract] public class TinyPageInfo { /// /// Each property that is to be returned in the /// Json must be decorated with DataMember /// [DataMember] public string PageName { get; set; } [DataMember] public int PageID { get; set; } } [DataContract] public class FoundPagesAjaxResponse { public FoundPagesAjaxResponse() { Pages = new List<TinyPageInfo>(); } [DataMember] public List<TinyPageInfo> Pages { get; set; } [DataMember] public string ResultListId { get; set; } /// /// Serialize the entire class to JSON /// /// public string ToJSON() { DataContractJsonSerializer serializer = new DataContractJsonSerializer(this.GetType()); MemoryStream ms = new MemoryStream(); serializer.WriteObject(ms, this); return Encoding.UTF8.GetString(ms.ToArray()); } }
This is how we handle the response from our server method in our ICallbackJsonPropertyControl_CallBack client function. As you can see in the script below it’s super easy to work with the JSON object we returned, it’s just like working with the .NET object.
// This is the method that will be called when the server has returned // a response. function ICallbackJsonPropertyControl_CallBack(jsonResponse, context) { // Use eval to turn the json into a simple to use object. var response = eval('(' + jsonResponse + ')'); // we passed the id of the listbox as part of the ajaxresponse object // and becuase we used eval above we can read that property // just like if it was the .net object var resultListBox = document.getElementById(response.ResultListId); // clear the listbox for (var i = resultListBox.options.length - 1; i >= 0; i--) { resultListBox.remove(i); } // Create options in the listbox for all pages // in the response. for (var i = 0; i < response.Pages.length; i++) { var option = document.createElement("OPTION"); option.text = response.Pages[i].PageName; option.value = response.Pages[i].PageID; resultListBox.add(option); } }
So there you have it! This technique works with any version of EPiServer supporting .NET 2.0+. However the JSON serialization part is only possible in .NET 3.5 but there’s nothing stopping you from using .NET 3.5 code with EPiServer 4. What’s nice about this solution is that you can distribute your property as a single dll with no dependencies on other frameworks etc. Of course you loose some of the goodness in existing ajax frameworks and you actually have to write actual code compared to using update panels. It’s obvious that this technique becomes a bit tedious if you actually have to make many different ajax request or create more visually appealing properties.
Download the full working project/code for this post
As usual it’s Visual Studio 2008 format and the code is built for EPiServer CMS R2 SP1.
Stay tuned for more parts in this series about ajax enabled properties!
With this blog I try to provide useful tips and solutions for programming .NET, Objective-C and more. My name is Björn Sållarp, and I love writing code.
Leave a reply