Click here to Skip to main content
Click here to Skip to main content

Connecting Dojo DataGrid to Entity Framework 4.1 using JsonRest Store and ASP.NET MVC

By , 17 Feb 2012
 
demo.png

Introduction

Dojo Toolkit is an open source modular JavaScript library (or more specifically JavaScript toolkit) designed to ease the rapid development of cross-platform, JavaScript/Ajax-based applications and web sites and provides some really powerful user interface features (Dojo Toolkit). One of the most powerful Dojo tools is DataGrid (DataGrid demo).

I wanted to use Dojo DataGrid with Entity Framework and ASP.NET MVC, but I couldn't find any complete sample about it. This article walks-through the process of creating a Dojo DataGrid to perform CRUD operations on the entities.

Creating Blog Entity

This project use Entity Framework Model First approach. But this isn't the point, you could also use Entity Framework Code First or Database First. Julie Lerman has a really good article about "Building an MVC 3 App with Model First and Entity Framework 4.1". You could use the article until you’ve got your model, your classes and your database in place, nothing more. We will make our controllers and views. Your model should be something like this:

model.png

RESTful Service within an ASP.NET MVC 3

As Dojo sends and receives JSON data to perform CRUD operations on the entities, so we need RESTful service within an ASP.NET MVC 3. You could find a good article about "Build a RESTful API architecture within an ASP.NET MVC 3 application" written by Justin Schwartzenberger at http://iwantmymvc.com/rest-service-mvc3. We won't use all of it, but I used parts of article idea.

First, we need a custom ActionFilterAttribute that we can craft to help us handle multiple verbs through a single controller action. Make a class (RestHttpVerbFilter.cs) at Model folder with code below:

using System.Web.Mvc;

namespace DojoDataGrid.Models
{
    public class RestHttpVerbFilter : ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            var httpMethod = filterContext.HttpContext.Request.HttpMethod;
            filterContext.ActionParameters["httpVerb"] = httpMethod;
            base.OnActionExecuting(filterContext);
        }
    }
}

"This code will capture the HTTP verb of the request and store it in the ActionParameters collection. By applying this attribute to a controller action, we can add a method parameter named httpVerb and the RestHttpVerbFilter will handle binding the HTTP request verb value to it. Our controller needs to support an action method with a common signature (the same parameters) but take different actions based on the HTTP verb. It is not possible to override a method with the same parameter signature but a different HTTP verb attribute. This custom attribute will allow us to have a single controller action method that can take action based on the HTTP verb without having to contain the logic to determine the verb."

-By Justin Schwartzenberger

BlogController

Now we need to create our controller.

AddController.png

Paste the code below in "BlogController.cs":

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Data.Entity;
using DojoDataGrid.Models;
using System.Data;
using System.Net;

namespace DojoDataGrid.Controllers
{
    public class BlogController : Controller
    {
        private BlogModelContainer db = new BlogModelContainer();

        // GET     /Blog/Action/
        // GET     /Blog/Action/3
        // POST    /Blog/Action    
        // PUT     /Blog/Action/3  
        // DELETE  /Blog/Action/3
        [RestHttpVerbFilter]
        public JsonResult Action(int? id, Blog blog, string httpVerb)
        {
            switch (httpVerb)
            {
                case "POST":
                    if (ModelState.IsValid)
                    {
                        db.Entry(blog).State = EntityState.Added;
                        db.SaveChanges();
                        return Json(blog, JsonRequestBehavior.AllowGet);
                    }
                    else
                    {
                        Response.TrySkipIisCustomErrors = true;
                        Response.StatusCode = (int)HttpStatusCode.NotAcceptable;
                        return Json(new { Message = "Blog " + id + 
                        " Data is not Valid." }, JsonRequestBehavior.AllowGet);
                    }
                case "PUT":
                    if (ModelState.IsValid)
                    {
                        db.Entry(blog).State = EntityState.Modified;
                        db.SaveChanges();
                        return Json(blog, JsonRequestBehavior.AllowGet);
                    }
                    else
                    {
                        Response.TrySkipIisCustomErrors = true;
                        Response.StatusCode = (int)HttpStatusCode.NotAcceptable;
                        return Json(new { Message = "Blog " + id + 
                        " Data is not Valid." }, JsonRequestBehavior.AllowGet);
                    }
                case "GET":
                    if (id == null)
                    {

                        IList<Blog> blogs = db.Blogs.ToList();
                        return Json(blogs, JsonRequestBehavior.AllowGet);
                    }
                    else
                    {
                        blog = db.Blogs.Find(id);
                        return Json(blog, JsonRequestBehavior.AllowGet);
                    }
                case "DELETE":
                    try
                    {
                        blog = db.Blogs.Single(x => x.Id == id);
                        db.Blogs.Remove(blog);
                        db.SaveChanges();
                        return Json(blog, JsonRequestBehavior.AllowGet);
                    }
                    catch
                    {
                        Response.TrySkipIisCustomErrors = true;
                        Response.StatusCode = (int)HttpStatusCode.NotAcceptable;
                        return Json(new { Message = "Could not delete Blog" + id }, 
                        JsonRequestBehavior.AllowGet);
                    }
            }
            return Json(new { Error = true, Message = "Unknown HTTP verb" });
        }
    }
}

As you can see, the BlogController performs "GET/POST/PUT/DELETE" in a single URL "/Blog/Action/" and it could do that because of RestHttpVerbFilter.

  • POST used to adding new blog
  • PUT used to editing a blog
  • GET used to sending a JSON data to grid containing all blogs or a blog
  • DELETE used to deleting a blog

If any error occurred, an error message will send with a Json data to grid.

HomeController

I edited the HomeController just for adding an action to generate some blog. You should make your HomeController like this:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using DojoDataGrid.Models;

namespace DojoDataGrid.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Message = "Connecting Dojo DataGrid to Entity 
                Framework using JsonRest Store and Asp .net MVC";

            return View();
        }

        public ActionResult generateSomeBlog()
        {
            try
            {
                BlogModelContainer db = new BlogModelContainer();

                Blog blog = new Blog();

                for (int i = 0; i < 10; i++)
                {
                    blog = new Blog();

                    blog.Title = string.Format("Title {0}", i + 1);
                    blog.BloggerName = string.Format("Blogger Name {0}", i + 1);

                    db.Blogs.Add(blog);
                }
                db.SaveChanges();
                ViewBag.Message = "Some Blogs have been generated";
            }
            catch { ViewBag.Message = "An Error occurred"; }

            return View();
        }
    }
}

Home/generateSomeBlog View

Make a view for generateSomeBlog action. It should be like this:

@{
    ViewBag.Title = "generateSomeBlog";
}
<h2>@ViewBag.Message</h2>

_Layout.cshtml

For adding a link to Generate Some Blog, you must edit menu part of "_Layout.cshtml" like this:

<ul id="menu">
    <li>@Html.ActionLink("Home", "Index", "Home")</li>
    <li>@Html.ActionLink("Generate Some Blog", "generateSomeBlog", "Home")</li>
</ul>

Home/Index View

Home/Index View should contain all the codes below:

@{
    ViewBag.Title = "Dojo Grid";
}
<h2>@ViewBag.Message</h2>

Dojo Data Grid
You could see a complete article about the code below in http://dojotoolkit.org/documentation/tutorials/1.7/store_driven_grid/.

As a point, this is so important that you use idProperty: "Id" in both JsonRest , Memory because Dojo DataGrid uses id as idProperty but our blog id is Id. This wastes my time. Hopefully, this tip can save someone else some time later.

dojo.query("body").addClass("claro"); adds "claro" theme to Grid and grid.canSort = function () { return false }; disables sorting of the grid because we didn't write any code in our controller to support it, and it needs a full article to talk about sorting and paging.

<link rel="stylesheet" 
href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/resources/dojo.css">
<link rel="stylesheet" 
href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dijit/themes/claro/claro.css">
<link rel="stylesheet" 
href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/grid/resources/Grid.css">
<link rel="stylesheet" 
href="http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojox/grid/resources/claroGrid.css">

<!-- load dojo and provide config via data attribute -->
<script src=http://ajax.googleapis.com/ajax/libs/dojo/1.7.1/dojo/dojo.js 
data-dojo-config="async: true, isDebug: true, parseOnLoad: true">
</script>
<script>
    var myStore, dataStore, grid;
    require([
                "dojo/store/JsonRest",
                "dojo/store/Memory",
                "dojo/store/Cache",
                "dojox/grid/DataGrid",
                "dojo/data/ObjectStore",
                "dojo/query",
                "dijit/form/Button",
                "dojo/domReady!"
            ], function (JsonRest, Memory, Cache, DataGrid, ObjectStore, query) {
                myStore = Cache(JsonRest({ target: "/Blog/Action/", idProperty: "Id" }),
                Memory({ idProperty: "Id" }));
                grid = new DataGrid({
                    store: dataStore = ObjectStore({ objectStore: myStore }),
                    structure: [
                        { name: "Blog Id", field: "Id", width: "50px" },
                        { name: "Title", field: "Title", width: "200px" },
                        { name: "Blogger Name", field: "BloggerName", width: "200px" }
                    ]
                }, "grid"); // make sure you have a target HTML element with this id
                
                grid.startup();

                dojo.query("body").addClass("claro");

                grid.canSort = function () { return false };
            });
            
</script>

<div style="height: 300px; width: 600px; margin: 10px;">
    <div id="grid">
    </div>
</div>

grid.png

The idea of code below came from Dojo Grid - Switching between editable and not editable: http://stackoverflow.com/questions/3703573/dojo-grid-switching-between-editable-and-not-editable with more functionality. You should ask why Edit mode and Add/Remove mode are separated. It is because if "Add/Remove" be in Edit mode, you could add a blog and before save you could edit it but the server doesn't assign an Id to blog yet and an error will occur. Also Remove is with Add, because you could remove a newly added blog before saving it.

<div id="normalMode">
    <button data-dojo-type="dijit.form.Button" id="editButton" onclick="editMode()">
        Edit</button>
    <button data-dojo-type="dijit.form.Button" 
    id="addRemoveButton" onclick="addRemoveMode()">
        Add / Remove</button>
</div>

normalMode

<div id="editMode" class="dijitHidden">
    <button data-dojo-type="dijit.form.Button" id="saveButton" onclick="saveTable()">
        Save</button>
    <button data-dojo-type="dijit.form.Button" 
    id="cancelEditButton" onclick="cancelTable()">
        Cancel</button>
</div>

editMode

<div id="addRemoveMode" class="dijitHidden">
    <button data-dojo-type="dijit.form.Button" 
    id="saveAddRemoveButton" onclick="saveTable()">
        Save</button>
    <button data-dojo-type="dijit.form.Button" id="addButton" onclick="addBlog()">
        Add</button>
    <button data-dojo-type="dijit.form.Button" id="removeButton" onclick="removeBlog()">
        Remove Selected Rows
    </button>
    <button data-dojo-type="dijit.form.Button" 
    id="cancelAddRemoveButton" onclick="cancelTable()">
        Cancel</button>
</div>

addRemoveMode.png

<br />
<div id="message">
</div>

<script>

    function addBlog() {
        var newBlog = { Title: "New Title", BloggerName: "New Blogger Name" };
        dataStore.newItem(newBlog);
    }

    function removeBlog() {
        var items = grid.selection.getSelected();
        if (items.length) {
            dojo.forEach(items, function (selectedItem) {
                if (selectedItem !== null) {
                    dataStore.deleteItem(selectedItem);
                }
            });
        }
    }

    function saveTable() {

        if (grid.edit.isEditing()) {
            grid.edit.apply();
        }

        if (dataStore.isDirty()) {
            dataStore.save({ onComplete: onSaveComplete, onError: onSaveError });
        }
    }

    function cancelTable() {
        if (grid.edit.isEditing()) {
            grid.edit.apply();
        }

        dataStore.revert();

       normalMode();
    }

    function onSaveComplete() {
        dojo.byId("message").innerHTML = ("Save done.");
        normalMode();
    }

    function onSaveError(response) {
        var errors = JSON.parse(response.responseText);

        for (var i in errors) {
            dojo.byId("message").innerHTML += errors[i];
        }

        normalMode();
    }

    function normalMode() {
        var theStructure = grid.structure;
        theStructure[1].editable = false;
        theStructure[2].editable = false;
        grid.set('structure', theStructure);

        dojo.removeClass("normalMode", "dijitHidden");
        dojo.addClass("editMode", "dijitHidden");
        dojo.addClass("addRemoveMode", "dijitHidden");
    }

    function editMode() {
        var theStructure = grid.structure;
        theStructure[1].editable = true;
        theStructure[2].editable = true;
        grid.set('structure', theStructure);
        //Clear any previous messages
        dojo.byId("message").innerHTML = ("");

        dojo.removeClass("editMode", "dijitHidden");
        dojo.addClass("normalMode", "dijitHidden");
        dojo.addClass("addRemoveMode", "dijitHidden");
    }

    function addRemoveMode() {
        //Clear any previous messages
        dojo.byId("message").innerHTML = ("");

        dojo.removeClass("addRemoveMode", "dijitHidden");
        dojo.addClass("normalMode", "dijitHidden");
        dojo.addClass("editMode", "dijitHidden");
    }
               
</script>

Content/Site.css

In Site.css, find the Table section and remove the code below. Because default MVC CSS file deforms our grid.

/* TABLE
----------------------------------------------------------*/

table {
    border: solid 1px #e8eef4;
    border-collapse: collapse;
}

table td {
    padding: 5px;
    border: solid 1px #e8eef4;
}

table th {
    padding: 6px 5px;
    text-align: left;
    background-color: #e8eef4;
    border: solid 1px #e8eef4;
}

Run the Solution

Now it's the time to see the result. Build the solution and Click on Generate Some Blog, then edit some blogs and add/remove some others.

gridTest.png

As you could see in fireBug data will send or request throw Json REST.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Nikfazan
Software Developer (Senior)
Iran (Islamic Republic Of) Iran (Islamic Republic Of)
Member
نیک فزان

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionLoad Grid on button click (or onChange event) rather than page loadmemberdavem117 Jul '12 - 10:51 
AnswerRe: Load Grid on button click (or onChange event) rather than page loadmemberdavem117 Jul '12 - 15:04 
QuestionAccessing the ?sort(+SORT_BY_COLUMN) parametermemberdavem114 Jul '12 - 10:03 
AnswerRe: Accessing the ?sort(+SORT_BY_COLUMN) parametermemberNikfazan14 Jul '12 - 10:43 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parametermemberdavem114 Jul '12 - 12:25 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parametermemberdavem114 Jul '12 - 15:35 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parametermemberdavem114 Jul '12 - 16:31 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parametermemberdavem114 Jul '12 - 18:04 
SuggestionRe: Accessing the ?sort(+SORT_BY_COLUMN) parameter [modified]memberNikfazan15 Jul '12 - 7:29 
GeneralRe: Accessing the ?sort(+SORT_BY_COLUMN) parametermemberdavem116 Jul '12 - 6:44 
GeneralMy vote of 5membernatural-code10 Jun '12 - 13:07 
GeneralRe: My vote of 5memberNikfazan10 Jun '12 - 22:01 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 17 Feb 2012
Article Copyright 2012 by Nikfazan
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid