Making JsonResult easier with "JSON Model Objects"

Lately I've been working with a client-side control library known as KendoUI. KendoUI contains a grid control that ties seamlessly to a Kendo DataSource object. The nice thing about the DataSource object is that it takes in a set of CRUD URLs and uses them to read and send data as needed; I don't have to worry about manually making AJAX calls to the server to get or send data. In order to use the Kendo DataSource with ASP.NET MVC we need to setup a set of CRUD action methods that respond with JSON data to queries that Kendo makes to the CRUD URLs. In this tutorial I'm going to show you how to simplify that set of action methods by creating a JsonModel and get rid of those pesky circular references.


Let's start by looking at an example project that uses the Kendo Grid. The first thing we need to do is setup a couple entity objects that our grid will show.

public class Author  
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }

    public List<Book> Books { get; set; }
}

public class Book  
{
    public int Id { get; set; }
    public string Title { get; set; }
    public DateTime PublishDate { get; set; }
    public string Genre { get; set; }

    public Author Author { get; set; }
}

Take notice that our entities both have navigation properties that link to each other. An Author can have many books in it's Books list property and a Book has an Author. This is what is known as a circular reference. One object refers to another object which then also refers back to the parent object. This is not a problem (yet) I just want you to keep this fact in mind. Now that we have our objects lets go take a look at the client-side code and setup the Kendo Grid.

First lets take a peek at what the Kendo DataSource looks like so we know how to structure our action methods.

var gridDataSource = new kendo.data.DataSource({  
    transport: {
        create: { url: '@Url.Action("AddBook", "Books")' },
        read: { url: '@Url.Action("GetBooks", "Books")' },
        update: { url: '@Url.Action("UpdateBook", "Books")' },
        destroy: { url: '@Url.Action("DeleteBook", "Books")' }
    }
});

I cut out a lot of irrelevant code from the above snippet so that we could get straight to the core. As you can see we have setup our CRUD URLs and pointed them at several action methods. The next step would seem to be pretty straightforward. First we want a controller to serve up our view with our grid code. I created a controller called BooksController with a single index action method.

public class BooksController : Controller  
{
    private DataContext _dataContext;

    public BooksController(DataContext dataContext)
    {
        _dataContext = dataContext;
    }

    // Return initial view containing code for Kendo grid.
    public ViewResult Index()
    {
        return View();
    }
}

Now that we have our controller we can start adding on our CRUD methods. Hopefully it's obvious to you that these methods need to return JsonResult. The Json() method accepts C# objects and serializes them into JSON strings. In our case we want to return an array of JSON objects; to do that all you do is pass a list of objects into Json().

public JsonResult GetBooks()  
{
    return Json(_dataContext.Books);
}

Can you identify what is wrong with the above method? If you didn't already know, the above method will fail at runtime with a "circular reference" exception. Remember above when I talked about circular references? To illustrate this let me show you what the Json() serialize method actually does.

Let's say I have two objects like this:

public class Foo  
{
    public string Message { get; set; }
    public Bar Bar { get; set; }
}

public class Bar  
{
    public string Message { get; set; }
}

Now lets create instances of them in a JsonResult action method.

public JsonResult GetFooBar()  
{
    var foo = new Foo();
    foo.Message = "I am Foo";
    foo.Bar = new Bar();
    foo.Bar.Message = "I am Bar";
    return Json(foo);
}

This action method would return the following JSON:

{
    "Message" : "I am Foo",
    "Bar" : {
        "Message" : "I am Bar"
    }
}

In this example we got exactly what we expected to get. While serializing foo it also went into the Bar property and serialized that object as well. However, let's mix it up a bit and add a new property to Bar.

public class Bar  
{
    public string Message { get; set; }
    public Foo Foo { get; set; }
}

Now lets set this property in our action method.

public JsonResult GetFooBar()  
{
    var foo = new Foo();
    foo.Message = "I am Foo";
    foo.Bar = new Bar();
    foo.Bar.Message = "I am Bar";
    foo.Bar.Foo = foo;
    return Json(foo);
}

This little addition will now cause this method to fail with a circular reference exception. Still don't understand why? Let's pretend to serialize foo ourselves.

{
    "Message" : "I am Foo",
    "Bar" : {
        "Message" : "I am Bar",
        "Foo" : {
            "Message" : "I am Foo",
            "Bar" : {
                "Message" : "I am Bar",
                "Foo" : {
                    "Message" : "I am Foo",
                    "Bar" : ...and on and on and on and on...
                }
            }
        }
    }
}

As you can see, the serializer would continue forever and ever because of the circular reference so instead it throws an exception. Sometimes, like in our Author and Book example, a circular reference is unavoidable. We can't always simply remove a property from one of our objects in order to satisfy the serializer. Anyone who has used a fair amount of LINQ already knows the trick to get around this. You build an anonymous type object to use as a projection of the original object. Basically we are building a new object that only has the properties we need in our JSON and doesn't have any circular references.

public JsonResult GetFooBar()  
{
    var foo = new Foo();
    foo.Message = "I am Foo";
    foo.Bar = new Bar();
    foo.Bar.Message = "I am Bar";
    foo.Bar.Foo = foo;
    return Json(new {
        message = foo.Message,
        barMessage = foo.Bar.Message
    });
}

In our previous example Author actually has a collection of Book so we use the LINQ Select() method to create a projection for each object in the collection. We'll add properties to this projected object that include values from the parent object so we can still get values from the parent but don't need to serialize child objects.

public JsonResult GetBooks()  
{
    return Json(_dataContext.Books.Select(x => new {
        title = x.Title,
        publishDate = x.PublishDate,
        genre = x.Genre,
        authorName = x.Author.Name
    });
}

So already we have a decent solution for avoiding circular references and it even decreases the amount of data going to the client because we're only sending what we are actually going to use. However, there is still a small annoyance which manifests itself in particular situations, such as creating CRUD methods for the Kendo DataSource. Take a look at this set of CRUD methods.

// Create
public JsonResult AddBook(Book book, string authorName)  
{
    var author = _dataContext.Authors.SingleOrDefault(x => x.Name == authorName);
    if (author == null)
        author = new Author { Name = authorName };
    author.Books.Add(book);
    _dataContext.SaveChanges();
    return Json(new {
        title = book.Title,
        publishDate = book.PublishDate,
        genre = book.Genre,
        authorName = book.Author.Name
    });
}

// Read
public JsonResult GetBooks()  
{
    return Json(_dataContext.Books.Select(x => new {
        title = x.Title,
        publishDate = x.PublishDate,
        genre = x.Genre,
        authorName = x.Author.Name
    });
}

// Update
public JsonResult UpdateBook(int id, string authorName)  
{
    var book = _dataContext.Books.Find(id);
    book.Author.Name = authorName;
    UpdateModel(book);
    _dataContext.SaveChanges();
    return Json(new {
        title = book.Title,
        publishDate = book.PublishDate,
        genre = book.Genre,
        authorName = book.Author.Name
    });
}

// Delete
public void DeleteBook(int id)  
{
    var book = _dataContext.Books.Find(id);
    _dataContext.Books.Remove(book);
    _dataContext.SaveChanges();
}

Since Kendo controls how and when to communicate with the CRUD URLs we have to follow their convention with what we return from the methods. For example, when Kendo talks to the "create" method it expects you to return the updated object. This is how Kendo knows the operation was successful. It also allows you to modify data on the server before sending it back and Kendo will update its client-side record accordingly. An example would be if the user set a property that was later determined to be invalid by the server then the server could set the property to some valid value and send it back and the Kendo grid would reflect that modification that happened on the server. Another example is the Id property; the database is often responsible for setting this value when you create a record so by sending the object back to the client after the record was added on the server Kendo is able to update the grid to show the generated ID.

One problem with this however is that we now have to repeat our anonymous type objects three times. If you ever make a change to this object you will also have to modify all three of these anonymous types. Fortunately, with a little creativity we can solve this problem fairly easily using what I call a JsonModel. It's a simple class that accepts the original object in its constructor and then assigns all its properties for you. We can make the example above much simpler by creating a BookJsonModel.

public class BookJsonModel  
{
    public BookJsonModel(Book book)
    {
        this.title = book.Title;
        this.publishDate = book.PublishDate;
        this.genre = book.Genre;
        this.authorName = book.Author.Name;
    }

    public string title { get; set; }
    public string publishDate { get; set; }
    public string genre { get; set; }
    public string authorName { get; set; }
}

Notice that I kept the first letter of my property names lowercase since I know they are going to be turned into JSON and the convention there is to have them lowercase. Now that we have this simple JSON View Model we can use it to simplify the code in our create, read, and update methods.

// Create
public JsonResult AddBook(Book book, string authorName)  
{
    var author = _dataContext.Authors.SingleOrDefault(x => x.Name == authorName);
    if (author == null)
        author = new Author { Name = authorName };
    author.Books.Add(book);
    _dataContext.SaveChanges();
    return Json(new BookJsonModel(book));
}

// Read
public JsonResult GetBooks()  
{
    return Json(_dataContext.Books.Select(x => new BookJsonModel(x));
}

// Update
public JsonResult UpdateBook(int id, string authorName)  
{
    var book = _dataContext.Books.Find(id);
    book.Author.Name = authorName;
    UpdateModel(book);
    _dataContext.SaveChanges();
    return Json(new BookJsonModel(book));
}

Here you learned how to create a JSON model object to use when passing data to the client.
Next check out how to extend our JSON model to help with model binding when sending data back to the server.

Chev

Read more posts by this author.

comments powered by Disqus