How To Call the Kentico CMS WebService from a JavaScript method
Reader Tip - Cick the Hide Sidebars link at the top right toolbar on this page to give you some more room to read this post on the screen, because it is code heavy.
The Kentico CMS WebService is a little known tool when it comes to the functionality of Kentico. It is used in a few Web Parts, the Repeater for web service Web Part comes to mind right away.
The WebService’s job is pretty straightforward, it’s main purpose, as you could probably guess, is to return data from the Kentico database via a SOAP call.
In this post I will show you how to enhance the built in functionality to also allow you to directly call the Kentico WebService from a JavaScript method. This is useful if you want to have some rich interaction on the client side of your pages such as creating a dynamic menu on the fly, or creating a rotating image banner.
To start out, notice that there is an empty WebService sitting around in your project already. It is located at:
~/CMSPages/WebService.asmx
Now if you aren't familiar with an ASP.NET WebService notice that this looks like an empty page. That is OK. There only needs to be one declarative that states the class name and where the code for the class exists. The code behind file for the existing WebService is located at:
~/App_Code/CMSPages/WebService.cs
Opening that up shows us a pretty basic example of a WebService.
[WebService(Namespace = "http://tempuri.org/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class WebService : System.Web.Services.WebService { /// /// Constructor. /// public WebService() { } /// /// Returns the data from DB /// /// String parameter for sql command [WebMethod] public DataSet GetDataSet(string parameter) { // INSERT YOUR WEB SERVICE CODE AND RETURN THE RESULTING DATASET return null; } }
Now lets make some changes to this WebService code that allows you to call it as a ScriptService. And don’t worry, all of the changes that we make won’t break any of the ways this service is used already in the system, we are only adding functionality.
What’s a ScriptService you may ask ? Well a ScriptService could be defined as a special kind of WebService that knows to serialize each web method’s results as JSON.
using System; using System.Web; using System.Collections; using System.Web.Services; using System.Web.Services.Protocols; using System.Data; using System.Data.SqlClient; using System.Configuration; using System.Web.Script.Services; // ADDED /// /// WebService template /// [ScriptService] // ADDED [WebService(Namespace = "http://webservices.mcbeev.com/")] [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class WebService : System.Web.Services.WebService { /// /// Constructor. /// public WebService() { } /// /// Returns the data from DB /// /// String parameter for sql command [WebMethod] [ScriptMethod] // ADDED public DataSet GetDataSet(string parameter) { DataSet ds = null; //Code to query the CMS Tree and Return Items return ds; } }
You can test out your changes to see if it worked by building the web site and running it. After the start page opens up navigate to:
~/CMSPages/WebService.asmx/js
// if Debugging is enabled on web.config you can also hit
~/CMSPages/WebService.asmx/jsdebug
You should get a response that includes some dynamically generated Javascript that looks like this: (the jsdebug version is not minimized and includes more info, as well as your comments)
var WebService=function() { WebService.initializeBase(this); this._timeout = 0; this._userContext = null; this._succeeded = null; this._failed = null; }
..... abbreviated ...
WebService.GetDataSet= function(parameter,onSuccess,onFailed,userContext) {WebService._staticInstance.GetDataSet(parameter,onSuccess,onFailed,userContext); } var gtc = Sys.Net.WebServiceProxy._generateTypedConstructor; Type.registerNamespace('System.Data'); if (typeof(System.Data.DataSet) === 'undefined') { System.Data.DataSet=gtc("System.Data.DataSet"); System.Data.DataSet.registerClass('System.Data.DataSet'); }
This result we are now seeing is a generated JavaScript proxy class. It takes care of the heavy loading and serialization of methods, responses, and complex types so that the client side knows how to handle using each of these responses and/or complex types. The key is at the bottom, the WebMethod we made above is now callable via WebService.GetDataSet(…) as long as we have this reference on our page somewhere.
Note that if your ASPX Page Template or Portal Page Template has a ScriptManager control on it, you wouldn't have to manually add in the script src tag. You could add a reference to the WebService in the ServiceReference collection property of the ScriptManager. Next, we need to write the followign JavaScript somewhere on our page:
function testService(){ WebService.GetDataSet("", serviceSuccess, serviceFailed); } function serviceSuccess(results){ if(results) { alert("worked"); } } function serviceFailed(error){ alert("Stack Trace: " + error.get_stackTrace() + "/r/n" + "Error: " + error.get_message() + "/r/n" + "Status Code: " + error.get_statusCode()); }
The testService method above would hit our new ScriptService asynchronously, and it should either return a result that alerts us that the call worked, or that it failed and why. There is a ton more you can do with ASP.NET AJAX WebServices but I don’t want to get off on a tangent on that. Just click through that link and read up more on it if you are interested or need more guidance.
Now lets put together a final example that grabs some data from the Kentico system and returns it to the client side JavaScript.
Modify the WebService.cs class to add in this WebMethod as follows:
/// /// Returns node names from the CMS Document Tree based on the path parameter /// /// Path to search the Tree, such as /% /// List of TreeNodes [WebMethod] [ScriptMethod] public List GetTreeNodeNames(string PathParameter) { if (string.IsNullOrEmpty(PathParameter)) throw new ArgumentException("No PathParameter specified"); int cacheMins = 30; bool condition = true; string cacheKeyName = "GetTreeNodeNames"; DataSet ds = null; List treeNodes = new List(); using (CachedSection cs = new CachedSection(ref ds, cacheMins, condition, null, cacheKeyName, PathParameter)) { if (cs.LoadData) { // Get from database ds = TreeHelper.SelectNodes(PathParameter, false, "CMS.MenuItem"); //cs.CacheDependency = ... cs.Data = ds; } } if (ds != null) { if (ds.Tables.Count > 0) { foreach(DataRow dr in ds.Tables[0].Rows) treeNodes.Add(dr["NodeName"].ToString()); } } return treeNodes; }
A few things to notice here. I'm using Martin Hejtmanek's new caching method to get my DataSet of information. There is really no reason to make a direct SQL call every time, so let's leverage the really awesome capabilities of the Kentico caching mechanism. This method exists in Kentico CMS 5.5+ only. Make sure you update your site before trying this.
Secondly, I’m not using the input parameter to specify some information so our new script, i.e the NodePath, we will have to pass that along as well in the JavaScript.
Thirdly, automatic serialization of complex types is great, like a DataSet, but the whole DataSet object that Kentico’s API returns is fairly heavy. Don’t try to return the whole thing in real world examples. Remember we are trying to keep this thing lightweight and fast. As you can see above I extract out the property I am looking to use and return in a more simple List of string. You can make your own DTO object here if you would like. A simple DTO should serialize just fine.
Updated JavaScript example:
function testService(){ WebService.GetTreeNodeNames("/Examples/%", serviceSuccess, serviceFailed); } function serviceSuccess(results){ if(results) { var nodes = results; for (var i = 0; i < nodes.length; i++) { alert(nodes[i]); } } } function serviceFailed(error){ alert("Stack Trace: " + error.get_stackTrace() + "/r/n" + "Error: " + error.get_message() + "/r/n" + "Status Code: " + error.get_statusCode()); }
And there you have it. Your test page should now be alerting some page names.