Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

DHTMLX and ASP.NET MVC

0.00/5 (No votes)
4 Nov 2009 1  
This article describes how DHTMLX components might be effectively used with ASP.NET MVC with extended routing functionality to build a flexible web application.

Introduction

In this article I would like to show you 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 the 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.

Most of the 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 the way explained below, you can send an AJAX request to the same URL and fire different controllers and actions. The required controller and action name are defined inside the posted data. I prefer to use data in JSON format because it is compact and is easy in the developing process. Besides, the .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 the dhtmlxTree component to build a simple directory manager application. The tree is used to show directory structure and has an input type=text box to define a new sub folder name.

Getting started

Client side

Once you have downloaded the latest dhtmlxTree version (you can grab it here), be sure to include dhtmlxtree_json.js to support the JSON data format. Drop the script into the \dhtmlx folder in the MVC project (I usually have all DHTMLX files inside the \dhtmlx folder, but you can keep them in your preferred folder, it doesn't matter). When you create an ASP.NET MVC project, Visual Studio places the required jQuery files in the \Scripts folder automatically. DHTMLX has enough components to build most 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 a div to keep the tree and an input type=text control to define a new folder name. The tree will be populated using onload, and will expand tree nodes 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, the controller name and the action name are defined inside the query string data, and so is the path to show the subfolders. The good thing about the dhtmlxTree is that it supports auto loading the required data. You do not need to build a big hierarchical structure on the server; just define setXMLAutoLoading and the tree will send requests to get the tree node children when there is a need to show them. Creating a sub folder uses jQuery functionality to send an 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 the parameters in the JSON object, and it looks like I will send data in JSON format, but it is not true. Even though I define all sending data in JSON format against the Prototype, jQuery automatically converts this JSON object to standard form data post string, and sends in the "application/x-www-form-urlencoded" content type. You need to use the Prototype or another tool to send data as a JSON string in x-json Content-Type. The request executed successfully, I get data as a JSON object and create a new tree node with tree.insertNewChild. In my example, the controller and action parameters are required for APS.NET MVC routing. In both examples, the controller is the same: Tree, but actions are different: List and 'CreateSubFolder'.

Routing

The default MVC routing looks for the controller and action names in the URL, but I pass them as parameters in the query string. MVC routing requires an explanation of how to get the required MVC data by extending the default functionality, and you can extend the routing to add supporting the MVC View name as a parameter. All "MVC routing requests" are handled by the MvcRouteHandler class, which will simply return an instance of the MvcHandler type. I create a custom routing handler and the 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 the route URL JSON/json with the custom route handler JSONRouteHandler and register the route in 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 the controller and the action name definitions. Our HTTP handler JSONMvcHandler overrides the ProcessRequest of the default MvcHandler to create a controller based on the controller name and to define the action name of the Route instance from the posted data. Another different posted data by the client is kept in DataTokens for later use 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 a simple ServiceAPI class container to extract data from the 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 the passed data in the QueryString and Form collections and JSON formats, and can be extended with another format determined by the "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 as this class only supports three types, I allow myself to ignore patterns. Of course, in a real project, you need to follow design patterns.) As I mentioned above, jQuery sends data to the server using the Content-Type: application/x-www-form-urlencoded, and we can easily go through the request form collection using the NameValueCollection instance to get the required controller, action, and other 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,>();
            }
            ((Dictionary<string,>)this.data).Add(key, collection[key]);
        }
    }
}

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

Controller

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

[AcceptVerbs("GET")]
public ActionResult List()
{
    string parentId = "0";
    string path = 
      Request.ServerVariables["APPL_PHYSICAL_PATH"] + this.workFolder;
    Dictionary<string,> data = 
      this.RouteData.DataTokens["data"] as Dictionary<string,>;
    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,> data = this.RouteData.DataTokens["data"] as Dictionary<string,>;
    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 at first, and next converts to JSON format with the View and calls the required View. The property names of the anonymous instance are identical as required by the 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 like a regular ASP.NET MVC controller with a few peculiarities: the directory name and path come from the DataTokens["data"] we saved in our JSONMvcHandler. And an important trick - our controller extends our base class JSONControllerBase which overrides the base ViewResult function. As a result, we get the 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 hardcode 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's good for you and it saves your developing time, then why not to have it.

View

In our example, the controller creates an anonymous object and saves it inside the ViewData dictionary with the key result, (ViewData["result"]), and calls View("json"). All the business logic of your application should be defined in the Model and sometimes in the Controller. The view should not keep any business logic, in my example, it just sends the required object to the client as a JSON string. I create a JSON View that clears any Response, because the IIS buffers response data, and writes the 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 another trick, the View is the ASP.NET page extended ViewPage. That’s it.

Final Thoughts

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

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here