In my last post, I replaced the default MVC JavascriptSerializer with the JSON.NET JsonSerializer and mentioned how using JsonIgnore attribute can help resolve circular reference error during JSON serialization. That was good to some extent, but it may not be ideal or enough for all the cases in your application. In this post, I want to show an example of how using JsonIgnore does not work and how using AutoMapper can help.
Imagine I have two models, Order and OrderLineItem, and I have set them up to persist by using an ORM, in this case EF Code-First. I’m not going to go into how that works because that’s not important for the purpose of this post. You could also use NHibernate instead because they both provide bi-directional association.
public class Order { public virtual int Id { get; set; } public virtual DateTime CreatedDate { get; set; } public virtual ICollection<OrderLineItem> LineItems { get; set; } } public class OrderLineItem { public virtual int Id { get; set; } public virtual int OrderId { get; set; } [JsonIgnore] public virtual Order Order { get; set; } }
There’s a one-to-many relationship between Order and OrderLineItem. And notice there’s a JsonIgnore attribute applied to the Order property in OrderLineItem to avoid the JSON serialization error.
Here is the OrderController with the Index action to return an Order by Id.
public class OrderController : BaseController { private readonly SimpleContext _simpleContext = new SimpleContext(); public ActionResult Index(int id) { Order order = _simpleContext.Orders.Find(id); return Json(order, JsonRequestBehavior.AllowGet); } }
For the sake of simplicity, I’m instantiating the EF DbContext directly. OrderController extends BaseController from my last post to provide JSON.NET integration and have the ReferenceLoopHandling set to Error in BaseController.
Now press F5 and start the app. When I make a request from the browser to /Order/Index/2, here is what I get.
In the response, we can see that Order with Id 2 contains 3 line items. Since we applied JsonIgnore to Order property, the serialization didn’t fail and we don’t see Order within OrderLineItem JSON.
Imagine we now need to get a particular order line item and at the same time its Order. To do that, we can add a new action called LineItem in Order controller.
public ActionResult LineItem(int id) { OrderLineItem lineItem = _simpleContext.OrderLineItems.Find(id); return Json(lineItem, JsonRequestBehavior.AllowGet); }
Build the changes and now make a request to /Order/LineItem/1. Here is what I get.
In the response, we can see that Order is missing because of the JsonIgnore attribute. But our requirement is we also need Order of the line item. There are a couple ways to handle this if you do a search on Google for “entity framework JSON circular reference”. But the way I prefer includes using AutoMapper.
With AutoMapper, we define DTOs and it’ll automatically map the properties from the models to the DTOs by convention. You can also configure the mapping using its fluent API if you want more control.
Install AutoMapper with NuGet. Create a OrderLineItemDto with the order information in it.
public class OrderLineItemDto { public int Id { get; set; } public int OrderId { get; set; } public DateTime OrderCreatedDate { get; set; } }
OrderLineItemDto.OrderCreatedDate is a property that will be mapped from OrderLineItem.Order.CreatedDate.
Open Global.asax and add this line to Application_Start() to bootstrap the AutoMapper configuration.
AutoMapperConfig.RegisterMappings(Mapper.Configuration);
Create class AutoMapperConfig in App_Start folder.
public class AutoMapperConfig { public static void RegisterMappings(IConfiguration configuration) { configuration.CreateMap<OrderLineItem, OrderLineItemDto>(); } }
Here I’m calling CreateMap directly for simplicity purpose. AutoMapper provides a Profile class if you want to organize your mapping logic in a more structured way.
In LineItem action of OrderController, update the code to use AutoMapper to map LineItem to its Dto.
public ActionResult LineItem(int id) { OrderLineItem lineItem = _simpleContext.OrderLineItems.Find(id); var dto = this.Map<OrderLineItemDto>(lineItem); return Json(dto, JsonRequestBehavior.AllowGet); }
And finally in BaseController, add the Map() function used in the OrderController.
protected T Map<T>(object target) { return AutoMapper.Mapper.Map<T>(target); }
Build and make the same line item request, we will now see the OrderCreatedDate in the response.
This may seem like a lot of code just to return Order information along with an line item. But most of the code is boilerplate code that just gets set up once and can be re-used by all other controllers. More importantly, we don’t see manually mapping logic in the controller action and we don’t need to disable any sort of ORM lazy loading functionality.
Here I’m just showing a little bit of AutoMapper functionality. For all the features that it provide, please refer to its wiki page.
With AutoMapper and JsonIgnore, hopefully they provide you with all the flexibility you need to return JSON data from MVC to your client.
Have fun coding!!