Using DHTMLX JavaScript Components in ASP.Net MVC Applications


Introduction

 

In this article I would like to show you the way how to build a flexible and scalable application with the help of dhtmlx components and ASP.Net MVC with extended routing functionality. To begin with, I'm going to assume that you are already familiar with jQuery, at least basically, as it is a part of ASP.Net MVC application by default. Let me also guess that you've heard about DHTMLX, an Ajax components library for building rich web UI. My goal is not to teach you how to use dhtmlx components, but rather how dhtmlx components might be effectively used with ASP.Net MVC with extended routing functionality to build a flexible and scalable application.


Most of dhtmlx components (such as grid, tree, combo, etc.) use the same client-server communication mechanism, and there is no need to describe it for each component. As an example I'll take dhtmlxTree to show you a fast way for web application development. Using explained bellow way you can send Ajax request to the same url and fire different controllers and actions. Required controller and action name are defined inside posted data. I prefer to use data in JSON format because it is compact and easy in developing process. Besides, .Net 3.5 framework has functionality to convert data to/from JSON format. You can create your own data format provider instead of JSON or XML and link up to the solution.


Further, I will demonstrate how to use dhtmlxTree component to build a simple directory manager application. The tree is used to show directory structure and input type=text box to define a new sub folder name.

 


Getting Started


Client Side


Once we have downloaded the latest dhtmlxTree component be sure you have file dhtmlxtree_json.js to support json data format, drop the script into \dhtmlx folder in MVC project (I usually have all dhtmlx files inside \dhtmlx folder, but you can keep them in your preferred folder, it does not matter). When you create ASP.Net MVC project, Visual Studio places required jQuery files in the \Scripts folder automatically. dhtmlx has enough components to build major functionality of common web applications and usually you will create html files for client-side functionality. Let's create a simple html file with div to keep tree and input type=text control to define a new folder name. The tree will be populated by onload and will be expanding tree node with the following javascript code:


    function doOnLoad() {

        tree = new dhtmlXTreeObject(document.getElementById('treeBox'), "100%", "100%", 0);

        tree.loadJSON("JSON/json?controller=Tree&action=List");

        tree.setImagePath("dhtmlx/imgs/");

        tree.attachEvent("onSelect", function() { });

        tree.setXMLAutoLoading(

         function(id) {

             tree.loadJSON("JSON/json?controller=Tree&action=List&path=" + getParents(id));

         }

      );

      tree.setXMLAutoLoadingBehaviour("function");

    }

 

Here controller name and action name are defined inside query string data the same as path to show subfolders. The good thing about dhtmlxTree is that it supports auto loading required data. You do not need to build big hierarchical structure on the server, just define setXMLAutoLoading and tree will send requests to get tree node children when there is a need show them. Creating sub folder uses jQuery functionality to send AJAX request.

 

    function createSubFolder() {

        var pId = tree.getSelectedItemId();

        var name = document.getElementById("folderName").value;

        if (name == "")

            return;

        var data = {

            'controller' : 'Tree'

            , 'action' : 'CreateSubFolder'

            , 'path': getParents(pId)

            , 'name' : name

        };

 

        jQuery.ajax({

        'type': "POST",

        'url': 'JSON/json',

        'data': data,

        'dataType': 'json',

        'error': function() { alert('Error occurred. Please contact the administrator') },

        'success': function(r) {

            tree.insertNewChild(pId, r.id, r.name, null, "folderClosed.gif", "folderOpen.gif", "folderClosed.gif");

            document.getElementById("folderName").value = '';

        }

        });

 

    }

 

NOTE: here I keep all parameters in the JSON object and it looks like I will send data in JSON format but it is not true. Even though I defined all sending data in the JSON format against Prototype the jQuery automatically converts this JSON object to standard form data post string and sends in "application/x-www-form-urlencoded" content type. You need to use Prototype or another tool to send data as json string in x-json Content-Type. Request executed successfully, I get data as JSON object and create a new tree node with tree.insertNewChild.

In my example controller and action parameters are required for APS.Net MVC routing. In both examples controller is the same 'Tree' but actions are different: List and 'CreateSubFolder'



Routing


Default MVC routing looks for controller and action names in the url but I pass them as parameters in the query string. MVC routing requires our explanation how get required MVC data with extending default functionality and you can extend a little described routing to add supporting MVC View name as parameters. All "MVC routing requests" are handled by the MvcRouteHandler class which will simply return an instance of the MvcHandler type. I create custom routing handler and associated http handler. The route handler derives from IRouteHandler and will be the class used when creating your json request routing.


    public class JSONRouteHandler : IRouteHandler
    {
        public IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            return new JSONMvcHandler(requestContext);
        }
    }


I define route url JSON/json with custom route handler JSONRouteHandler and register route in the Global.asax as: 

 routes.Add(new Route("JSON/json", new JSONRouteHandler()));

The http handler derives from MvcHandler because it gives me some critical information, like RequestContext, required for controller and action name definitions. Our http handler JSONMvcHandler overrides ProcessRequest of default MvcHandler to create controller based on controller name and define action name of Route instance from posted data. Another different posted data by client I keep in the DataTokens for later using in the controller.


        protected override void ProcessRequest(HttpContextBase httpContext)

        {

            ServiceAPI serviceAPI = new ServiceAPI(this.RequestContext.HttpContext.Request);

            IControllerFactory factory = ControllerBuilder.Current.GetControllerFactory();

            IController controller = factory.CreateController(RequestContext, serviceAPI.Controller);

            if (controller == null)

            {

                throw new InvalidOperationException(

                    String.Format(

                        "The IControllerFactory '{0}' did not return a controller for named '{1}'.",

                        factory.GetType(),

                        serviceAPI.Controller));

            }

            try

            {

                this.RequestContext.RouteData.Values.Add("controller", serviceAPI.Controller);

                this.RequestContext.RouteData.Values.Add("action", serviceAPI.Action);

                this.RequestContext.RouteData.DataTokens.Add("data", serviceAPI.Data);

                controller.Execute(this.RequestContext);

            }

            finally

            {

                factory.ReleaseController(controller);

            }

        }

 

Regarding our data definitions I create simple ServiceAPI class container to extract data from HttpRequestBase instance and keep it.


        public ServiceAPI(HttpRequestBase request)

        {

            // read data from query string

            this.populateFromCollection(request.QueryString);

            if (

                request.Headers["Content-Type"] != null

                && request.Headers["Content-Type"].Contains("x-json")

                )

            {

                // read data from stream if data sent in json format with Prototype for example

                this.populateFromJSONStream(request.InputStream);

            }

            else

            {

                // read data from form collection

                this.populateFromCollection(request.Form);

            }

        }


As you can see this class supports passed data in QueryString, Form collection and JSON formats and can be extended with another formats determined by "Content-Type". (I agree if you say that it'd be much better to keep each data processing functionality per content type in its own class, but design patterns are not the goal of this article and as it is a template application and this class supports three types only, I allow myself to ignore patterns. Of course, in a live project you need to follow design patterns.). As I mentioned above jQuery sends data to server in Content-Type: application/x-www-form-urlencoded and we can easy go through request form collection as NameValueCollection instance to get required controller, action and other sent data.

 

        private void populateFromCollection(NameValueCollection collection)

        {

            foreach (string key in collection.Keys)

            {

                if (key.Equals("controller"))

                {

                    this.controller = collection[key];

                }

                else if (key.Equals("action"))

                {

                    this.action = collection[key];

                }

                else

                {

                    if (this.data == null)

                    {

                        this.data = new Dictionary<string, object>();

                    }

                    ((Dictionary<string, object>)this.data).Add(key, collection[key]);

                }

            }

        }

 

If you send data in x-json format then it's necessary to convert hex input stream to ASCII string and deserialize with JavaScriptSerializer.



Controller


In my example controller name is Tree and action is List, CreateSubFolder ,and I create TreeController class corresponding public function:


        [AcceptVerbs("GET")]

        public ActionResult List()

        {

            string parentId = "0";

            string path = Request.ServerVariables["APPL_PHYSICAL_PATH"] + this.workFolder;

            Dictionary<string, object> data = this.RouteData.DataTokens["data"] as Dictionary<string, object>;

            if (data != null && data.ContainsKey("path"))

            {

                path += data["path"];

            }

 

            Models.Folder folder = new Models.Folder(path);

            if (data != null && data.ContainsKey("path"))

            {

                parentId = folder.Parent.Id;

            }

            ViewData["result"] = this.Folders2Tree(parentId, folder.GetChildren());

            return View("json");

        }

 

        [AcceptVerbs("POST")]

        public ActionResult CreateSubFolder()

        {

            string path = Request.ServerVariables["APPL_PHYSICAL_PATH"] + this.workFolder;

            Dictionary<string, object> data = this.RouteData.DataTokens["data"] as Dictionary<string, object>;

            if (data != null && data.ContainsKey("path"))

            {

                path += data["path"];

            }

 

            Models.Folder folder = new Models.Folder(path);

            Models.Folder newFolder = folder.CreateSubFolder(data["name"] as String);

            ViewData["result"] = new

            {

                id = newFolder.Id

                , name = newFolder.Name

            };

            return View("json");

        }


 This controller creates anonymous instances with this.Folders2Tree in first and plain for next converting to JSON format with View and calls required View. Property names of anonymous instance are identical as required by dhtmlxTree component because View("json") just converts to JSON.


        private object Folders2Tree(string rootId, IEnumerable<Models.Folder> folders)

        {

            var tree = new {

                id = rootId,

                item = new List<object>()

            };

            foreach (Models.Folder folder in folders)

            {

                tree.item.Add(new {

                    id = folder.Id

                    , text = folder.Name

                    , child = folder.HasChildren?"1":"0"

                    , im0 = "folderClosed.gif"

                    , im1 = "folderOpen.gif"

                    , im2 = "folderClosed.gif"

                });

            }

            return tree;

        }


The controller looks as a usual ASP.Net MVC controller with a few peculiarities: directory name and path are coming from DataTokens["data"] we saved in our JSONMvcHandler. And an important trick - our controller extends our base class JSONControllerBase overrides base ViewResult function. As a result we get customized flexible View definition.

 

    public abstract class JSONControllerBase : Controller

    {

        protected override ViewResult View(string viewName, string masterName, object viewData)

        {

            string noun = "JSON";

            string fullViewName = string.Format("~/Views/{0}/{1}.aspx", noun, viewName);

            return base.View(fullViewName, masterName, viewData);

        }

    }


Our controller calls View with parameter "json" (View("json")) and it is viewName parameter in the overridden View functionality. As you can see, we have a full control on View definition and we even can have one View for all controllers with hard coding fullViewName. You can hard code View name if your application requires JSON object interchanging only. As a result, your application will have just one View. It is not usual for ASP.Net MVC, but it is possible, and if it is good for you and it saves your developing time then why not to have it.



View


In our example the controller creates anonymous object and saves it inside ViewData dictionary with key result (ViewData["result"]) and calls View("json"). All business logic of your application should be defined in the Model and sometimes in the Controller. View should not keep any business logic, in my example it just sends required object to client as JSON string. I create json View that clears any Response because IIS buffered response data already and writes serialized ViewData["result"].


    public partial class json: ViewPage
    {
        protected override void  OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            this.SendResponse();
        }        

        private void SendResponse()
        {
            JavaScriptSerializer jss = new JavaScriptSerializer();
            StringBuilder output = new StringBuilder();
            jss.Serialize(ViewData["
result"], output);
            Response.Clear();
            Response.ContentType = "x-json";
            Response.Write(output.ToString());
            Response.Flush();
            Response.End();
        }
    }


Please note, it is another trick, View is ASP.Net page extended ViewPage.

 

That's it.



Final Thoughts


We've created one route JSON/json to process multiple requests to different controllers and different actions. Posted data keeps controller name and action name. We've also created one View to send data back to client browser from lots of different controllers. And what is important, we keep easy unit testing of controllers, just need to set required test object in the DataTokens["data"]. Furthermore, we can build easy regression testing functionality. We just need to define requests corresponding responses files and an application that will read request, send to server and compare received response with required corresponding response.

 

And one more flexible feature we got: client side and server side parts can be development independent. An application architect defines client-server application API and developers create test requests data and test JSON responses. Using these test data, client-side developers can develop client part without server part, and server-side developers can develop server part without client. Using this way you can complete an application much faster.


Similar Articles