Working With Entity Framework Code First And JSON In ASP.NET Web Pages

If you want to build AJAX powered rich client interfaces with ASP.NET Web Pages, you will work with JSON - a lot. You might also be attracted to the Entity Framework for your data access technology. There are some special considerations to take account of when serialising Entity Framework objects to JSON for use in Web Pages applications. This article explores some of the issues you will encounter through the example of a simple To Do list manager.

The sample application is very, very simple: it displays items from a To Do list and provides the means to add new items. The object model is also very simple. Each item in the To Do list is realised in code as a Task object. Each task results in an activity which is defined as a TaskType object. This exercise follows the Code First approach to using the Entity Framework. If you are not familiar with Code First and how to add the Entity Framework to your site, you might want to read my previous article covering the topic. Assuming you are confortable with the basics, here is the code for the entities for this application. First, the Task class:

using System;
using System.ComponentModel.DataAnnotations;

/// <summary>
/// Represents an item in a TO DO list
/// </summary>
public class Task
{
    [Required]
    public int TaskId { get; set; }
    [Required, MaxLength(100)]
    public string TaskTitle { get; set; }
    [Required, MaxLength()]
    public string Details { get; set; }
    [Required]
    public DateTime CompleteBy { get; set; }
    public bool IsDone { get; set; }
    public int TaskTypeId { get; set; }
    public virtual TaskType TaskType { get; set; }
}

Now the TaskType class:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

/// <summary>
/// Represents the expected output of a task 
/// </summary>
public class TaskType
{
    [Required]
    public int TaskTypeId { get; set; }
    [Required, MaxLength(50)]
    public string Activity { get; set; }
    public virtual ICollection<Task> Tasks { get; set; }
}

Both of these are placed a separate files (Task.cs and TaskType.cs) in the App_Code folder so that they are compiled when the application first runs. In addition, a class that derives from DbContext is needed:

using System.Data.Entity;

/// <summary>
/// Enables data access for entities
/// </summary>
public class TaskContext : DbContext
{
    public DbSet<Task> Tasks { get; set; }
    public DbSet<TaskType> TaskTypes { get; set; }
}

The DbSets will become tables in the database that gets generated by EF and an instance of TaskContext will provide the means to execute SQL against the database using C# code rather than SQL. The C# code will be based on the Repository pattern, with data access methods grouped by entity. The TaskRepository code is as follows:

using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Centralises data operations for Task objects
/// </summary>
public class TaskRepository
{
    TaskContext db = new TaskContext();

    public Task Save(Task task) {
        task.TaskType = db.TaskTypes.Find(task.TaskTypeId);
        if (task.TaskId == 0) {
            db.Tasks.Add(task);
        }
        else {
            var t = db.Tasks.Find(task.TaskId);
            if(task.IsDone){
                t.IsDone = task.IsDone;
            } else {
                t.TaskTitle = task.TaskTitle;
                t.Details = task.Details;
                t.CompleteBy = task.CompleteBy;
                t.TaskType = task.TaskType;
            }
        }
        db.SaveChanges();
        return db.Tasks.Find(task.TaskId);
    }



    public List<Task> GetAllTasks(){
        return db.Tasks.ToList();
    }
}

There are two methods. The first is a Save method, which covers both inserts and updates. It accepts a Task obejct as a parameter. If that Task object has no TaskId, it has yet to be created, so the Task is added as a new entry to the Tasks DbSet. If there is a TaskId, the object exists and needs to be modified. If IsDone is true, that is considered to be the only change required. Otherwise the Task properties are amended. Finally, the changes are committed to the database and the new or modified task object is returned. The second method returns all tasks returned as a generic List.

The TaskTypeRepository code consists of one method that returns all TaskType objects as a List:

using System;
using System.Collections.Generic;
using System.Linq;

/// <summary>
/// Centralises data operations for TaskType objects
/// </summary>
public class TaskTypeRepository
{
    TaskContext db = new TaskContext();
    
    public List<TaskType> GetAllTaskTypes(){
        return db.TaskTypes.ToList();
    }
}

These methods are going to be used by a set of services, which will be responsible for translating the entities generated by the repositories to JSON, and for translating JSON that is generated by the calling page into entity objects that the repository methods can work with. Each service will occupy its own .cshtml file. So I create a folder called JsonService, and add three files to it: GetAllTaskTypes.cshtml, GetAllTasks.cshtml and SaveTask.cshtml. GetAllTaskTypes.cshtml contains the following code:

@{
    var repository = new TaskTypeRepository();
    var taskTypeList = repository.GetAllTaskTypes().Select(t => new {
        TaskTypeId = t.TaskTypeId,
        Activity = t.Activity
    });
    Json.Write(taskTypeList, Response.Output);
}

The service uses the Json helper to generate JSON, but it doesn't serialise the List<TaskType> that the repository returns. The List<TaskType> is converted into an anonymous type and that is what is serialsed to JSON. You can see this where a LINQ Select extension method has been added to the repository method call, and a lambda expression takes each element in the sequence and converts it to a different type. However, no type is specified after the new keyword, so the compiler generates a type on the fly. Anonymous types are usually used as convenience containers for read-only property values.

Why convert the TaskType collection to an anonymous type as allt? Well, each TaskType object has a reference to a Task collection, and each Task object has a reference to a TaskType object. The serialiser within the Json helper cannot cope with the resulting circular reference, and if an attempt is made to serialise TaskType objects, or Task object, an exception is thrown:

Exception Details: System.InvalidOperationException: A circular reference was detected while serializing an object of type 'System.Data.Entity.DynamicProxies.<T> (where T is the type being serialised.)

There are a couple of ways to prevent this, but the simplest is to "flatten" the data and thereby remove any references it has prior to serialising it. This is achieved by projecting it into a new form, and an anonymous type is perfect for that. The same approach using an anonymous type is taken in the GetAllTtasks.cshtml code:

@{
    var repository = new TaskRepository();
    var taskList = repository.GetAllTasks().Select(t => new {
        TaskId = t.TaskId,
        TaskTitle = t.TaskTitle,
        Details = t.Details,
        CompleteBy = t.CompleteBy.ToString("yyyy/MM/dd"),
        IsDone = t.IsDone,
        Activity = t.TaskType.Activity
    });
    Json.Write(taskList, Response.Output);
}

The Task object's CompleteBy property is a DateTime data type. Left alone, it will serialise in a particular format, ege: \/Date(1343430000000)\/ where the number represents the elapsed milliseconds since Jan 1 1970. That would require some manipulation in client code to get it to appear as a recognisable date. If I was exposing the JSON to third parties I would probably just allow the JSON helper to do its thing and let others worry about how to display the date. But as this is for internal use only, I choose to convert the value to a string on the server, and then serialise that. It saves having to construct some additional JavaScript for formatting the value in the browser.

The final "service" for this application is the one that is responsible for taking a new or modified Task and saving it to the database, which is the responsibility of SaveTask.cshml:

@{
    var reader = new StreamReader(Request.InputStream);
    var json = reader.ReadToEnd();
    var task = Json.Decode<Task>(json);
    var repository = new TaskRepository();
    var t = repository.Save(task);
    var savedTask = new {
        TaskId = t.TaskId,
        TaskTitle = t.TaskTitle,
        Details = t.Details,
        CompleteBy = t.CompleteBy.ToShortDateString(),
        IsDone = t.IsDone,
        Activity = t.TaskType.Activity
    };
    Json.Write(savedTask, Response.Output);
}

There are a number of ways in which you can transfer an object from client-side JavaScript to the server via AJAX. The first is to serialise an HTML form, using POST. You can obtain the values from Request[form-field-name] on the server. You could also send a bunch of name/value pairs in the query string using GET, also accessible via Request[form-field-name]. Or you can construct a JSON object and send it in the body of a POST request. If you do that, the value is available in the InputStream property of the Request object. That is0 how the SaveTask service expects a Task object to arrive - in the body of the POST request. The value is stored in a variable called json once it has been extracted from the InputStream, and then is converted into a C# Task obejct using the strongly typed Json.Decode<T> method, where T represents the type to decode the JSON into. That Task object is passed ot the repository's Save method which will either add a new task or modify and existing one, and then return the new or modified task back to the service. That is prepared for serialisation and sent back to the client.

So to recap - we have our objects defined - Task and TaskType. We have a way of mapping those objects to database tables via our TaskContext object which derives from DbContext. We have some simple data access methods in repository classes, and we have a way of making use of those data access methods from client-side Javascript. We do not actually have a database, neither do we have any client-side code for displaying or adding new task objects. We'll take care of the lack of database first.

This sample won't provide a means to create TaskTypes from the user interface, so they will be created from code. One of the features of Code First is that the database is not generated until code attemtps to make use of it, so we can kill both birds with one stone by adding some code in and AppStart file:

@using EF = System.Data.Entity;
@{
    EF.Database.SetInitializer(new EF.DropCreateDatabaseIfModelChanges<TaskContext>());
    var db = new TaskContext();
    db.TaskTypes.Add(new TaskType {Activity = "Email"});
    db.TaskTypes.Add(new TaskType {Activity = "Meeting"});
    db.TaskTypes.Add(new TaskType {Activity = "Document"});
    db.TaskTypes.Add(new TaskType {Activity = "Phone Call"});
    db.TaskTypes.Add(new TaskType { Activity = "Letter" });
    db.SaveChanges();
}

AppStart executes when the site first starts. Consequently, this code will result in the database being created and a series of task types being added to the TaskType table. If the site is restarted, the TaskTypes will be added again, so you should remember to remove the code that does this once the database has been created and populated.

That just leaves the user interface, which can be accommodated in a single page. Here it is in its entirety:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>TODO Manager</title>
        <link href="~/Content/site.css" rel="stylesheet" type="text/css" />
        <link href="~/Content/jquery-ui-1.8.21.custom.css" rel="stylesheet" type="text/css" />
        <script src="~/Scripts/jquery-1.7.2.min.js" type="text/javascript"></script>
        <script src="~/Scripts/jquery-ui-1.8.21.custom.min.js" type="text/javascript"></script>
        <script type="text/javascript">
            //Controls appearance of new tasks
            function slide(panel, content){
                $('#' + panel).slideDown(600, function(){
                    $('#' + content).animate(
                        {
                            opacity: '1'
                        },  600
                    );
                });
            }

            $(function () {
                // Prevent browser caching
                $.ajaxSetup({ cache: false });
                
                var taskList = $('#tasks');
                var select = $('#TaskTypeId');

                //Get all tasks and display them
                $.getJSON('/JsonService/GetAllTasks', function(tasks) {
                    $.each(tasks, function(index, task) {
                        var panel = 'panel' + task.TaskId;
                        var content = 'content' + task.TaskId;
                        var html = '<div id=\'' + panel + '\' class=\'panel\'><div id=\'' + content + '\' class=\'content\'>';
                        html += '<p><strong>' + task.TaskTitle + '</strong></p>';
                        html += task.Details + '<br />';
                        html += 'Complete By: ' + task.CompleteBy + '<br />';
                        html += task.Activity + '</div></div>';
                        taskList.prepend(html);
                        slide(panel, content);
                    });
                });

                // Populate the dropdwnlist with Task Types
                $.getJSON('/JsonService/GetAllTaskTypes', function(taskTypes) {
                    select.empty();
                    select.append($('<option/>').attr('value', '').text('--Choose Task Type--'))
                    $.each(taskTypes, function(index, taskType) {
                        select.append($('<option/>').attr('value', taskType.TaskTypeId).text(taskType.Activity));
                    });
                });

                // Set up the date picker
                $('#CompleteBy').datepicker({ dateFormat: 'yy/mm/dd' })

                // Open the modal form on the button click 
                $('#add-task').click(function() {
                    $('#new-task').dialog('open');
                });

                // Convert the div to a modal dialog form
                $('#new-task').dialog({
                    autoOpen: false,
                    modal: true,
                    height: 315,
                    width: 500,
                    buttons: {
                        'Add Task': function () {
                            // Create a new task object
                            var task = {
                                TaskTitle: $('#TaskTitle').val(),
                                Details: $('#Details').val(),
                                CompleteBy: $('#CompleteBy').val(),
                                TaskTypeId: $('#TaskTypeId').val()
                            };
                            // Add the new task and display it
                            $.ajax("JsonService/SaveTask", {
                                data: JSON.stringify(task),
                                type: "post",
                                contentType: "application/json",
                                success: function(data) {
                                    var savedTask = JSON.parse(data);
                                    var panel = 'panel' + savedTask.TaskId;
                                    var content = 'content' + savedTask.TaskId;
                                    $('#new-task').dialog('close');
                                    var html = '<div id=\'' + panel + '\' class=\'panel\'><div id=\'';
                                    html += content + '\' class=\'content\'>';
                                    html += '<p><strong>' + savedTask.TaskTitle + '</strong></p>';
                                    html += savedTask.Details + '<br />';
                                    html += 'Complete By: ' + savedTask.CompleteBy + '<br />';
                                    html += savedTask.Activity + '</div></div>';
                                    taskList.prepend(html);
                                    slide(panel, content);
                                    $('#TaskTitle').val('');
                                    $('#Details').val('');
                                    $('#CompleteBy').val('');
                                    $('#TaskTypeId').val('');
                                }
                            });
                        },
                        Cancel: function() {
                            $('#new-task').dialog('close');
                        }
                    }
                });
            });
        </script>
    </head>
    <body>
        <button id="add-task">Add Task</button>
        <div id="tasks"></div>
        <div id="new-task" title="New Task">
            <div class="editor-row">
                <span class="editor-label">Title:</span>
                <span class="editor-field">@Html.TextBox("TaskTitle", null, new {size = 40})</span>
            </div>
            <div class="editor-row">
                <span class="editor-label">Details:</span>
                <span class="editor-field">@Html.TextArea("Details", new {rows = 6, cols = 50})</span>
            </div>
            <div class="editor-row">
                <span class="editor-label">Complete By:</span>
                <span class="editor-field">@Html.TextBox("CompleteBy")</span>
            </div>
            <div class="editor-row">
                <span class="editor-label">Task Type:</span>
                <span class="editor-field">@Html.DropDownList("TaskTypeId", Enumerable.Empty<SelectListItem>())</span>
            </div>
        </div>
    </body>
</html>

Starting at the bottom, you can see a button, an empty div and a series of form fields, but no form element. The empty div called tasks is used to display the list of tasks, and the button is used to invoke the jQuery dialog whcih will display the form fields. The task type selector is generated using the Html.DropDownList helper. It is prepopulated with an empty collection. When the page loads, a jQuery getJSON method is called that obtains the TaskType data from GetAllTaskTypes.cshtml and populates the dropdown list with them. Another getJSON call is repsonsible for obtaining any existing Tasks and displying them in the empty div. When the dialog fields are completed and the Save button is clicked, a JavaScript Task object is created from the inputs and then converted to JSON using JSON.stringify (a browser method, not specific to jQuery) and posted to the SaveTask.cshtml service. The return value is added to any that already appear, and a bit of jQuery animation is used to jazz the appearance up a bit. You can clone the GitHub repo that features the code in this article and experiment with it as you like. It is set up ready to create the database and populate TaskTypes on the first run.

Summary

The key concepts covered in this article are: how to design Entity Framework data access based on the Repository pattern; the need to flatten Entity Framework objects before serialising to JSON; how to use JSON.stringify() to convert a JavaScript object to JSON; how to transfer JSON in the Request body and access it via Request.InputStream; and the use of Json.Decode<T> to convert JSON to a type that your C# code understands and can work with.

 

Date Posted: Sunday, July 22, 2012 8:46 PM
Last Updated: Friday, December 14, 2012 9:57 PM
Posted by: Mikesdotnetting
Total Views to date: 76080

2 Comments

Thursday, August 2, 2012 10:31 PM - Mauro Cesar Otoni

Thanks for the tutorial, but the download link does not work.

I would like if possible to receive an example on how to update data via JSON WebGrid, jquery.
The User enters the name into a text box, and WebGrid is updated with the database to JSON.

I am Brazilian, and please excuse me bad English.

Friday, August 3, 2012 9:44 AM - henry

Hi Mike
I am unable to download the sample site. Can you please test it and let me know.
Thank you for your great articles and effort in general!
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

Bino 11/27/2014 7:05 PM
In response to MVC 5 with EF 6 in Visual Basic - Async and Stored Procedures with the Entity Framework
Copy +...

Manas 11/27/2014 5:30 AM
In response to Scheduled Tasks In ASP.NET With Quartz.Net
Hi Mike, Thank you for awesome article. My concern is it might impact website if we use or is...

priya 11/26/2014 6:50 PM
In response to Create PDFs in ASP.NET - getting started with iTextSharp
very nice.....its save my time...

ransems 11/24/2014 12:29 AM
In response to Adding A Controller
Love the article. I dislike that the world thinks c# articles are the way to go. Love the VB, keep...

Gautam 11/20/2014 8:01 AM
In response to I'm Writing A Book On WebMatrix
Hello Mike, I read your book, loved it! However, I have a few request/suggestions: 1) an example...

Bret Dev 11/19/2014 8:39 PM
In response to The Difference Between @Helpers and @Functions In WebMatrix
Excellent post! One concern - where can you place global @Functions code within an MVC project to Is...

Rob Farquharson 11/19/2014 4:28 PM
In response to iTextSharp - Links and Bookmarks
How can I place text at an absolute position on the page? Also, how can I rotate text?...

Andy 11/17/2014 8:08 PM
In response to MVC 5 with EF 6 in Visual Basic - Sorting, Filtering and Paging
Hello I'm testing your sorting instructions above. This is great and I was able to get it to work...

Gautam 11/17/2014 5:51 PM
In response to WebMatrix - Database Helpers for IN Clauses
Hi Mike, I am very new to programming: In the above example if I want to use a delete button the...

donramon 11/17/2014 3:22 PM
In response to Entity Framework 6 Recipe - Alphabetical Paging In ASP.NET MVC
Congratulations on your new website look and the excellent articles. Thank you!...