Handle Cyclical References with ASP.NET Web API 2 and MVC 6
Many ORM (object-relational mapping) tools, such as Entity Framework 6 Tools for Visual Studio 2012 & 2013, Entity Framework Power Tools, or Entity Framework Reverse POCO Generator, generate entity classes that contain cyclical references.
Download the source code, samples, and NuGet packages for this blog post:
- AspNet WebApi 2 Helpers (Asp.Net 4 / vCurrent)
- AspNet Mvc 6 Helpers (Asp.Net 5 / vNext)
Say, for example you have two classes: Product and Category.
public partial class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int? CategoryId { get; set; }
public Category Category { get; set; } // References Category
}
public partial class Category
{
public Category()
{
Products = new HashSet<Product>();
}
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<Product> Products { get; set; } // References Product
}
Product has a Category property, and Category has a Products property. This is all fine, except when you attempt to return those entities from a web service, in which case the serializer will complain that the object graph for the type contains cycles and cannot be serialized. Here you see the exception generated by the XML (Data Contract) serializer.
Here is the exception generated by Json.Net.
There are a number of ways to solve this problem. First, you could eliminate the cyclical reference in entities returned by the service. For example, just remove the Products property from Category, and the error disappears. This works if you use the DTO (data transfer objects) pattern, perhaps utilizing a class mapping tool.
If you don’t want to maintain a separate set of classes, your other alternative would be to give a hint to the serializer to serialize object references instead of values. This can be done with attributes places on classes and/or properties. Json.Net has a JsonObject attribute with an IsReference parameter which you can set to true. Similarly, the DataContract attribute also has an IsReference property, but using it means you have to decorate every property you want included with a DataMember attribute. Not only is the resulting code messy, but polluting classes with serialization attributes violates the POCO (plain old CLR object) principle by coupling entities to specific infrastructure concerns. And to include them in your entities, you’d need to modify the T4 template used for the code generation, which isn’t all that easy. Otherwise, your changes will be wiped out the next time you re-generate your entity classes. Your task is further complicated should you decided to support another serialization format, such as Protobuf.
// Should entities be aware of serialization concerns?
[JsonObject(IsReference = true)]
[DataContract(IsReference = true)]
public partial class Product
{
[DataMember]
public int ProductId { get; set; }
[DataMember]
public string ProductName { get; set; }
[DataMember]
public int? CategoryId { get; set; }
[DataMember]
public Category Category { get; set; }
}
If you’re not using DTO’s and you’d rather not liter your POCO’s with attributes, a better approach would be to configure the serializers programmatically. And that’s what my serialization helpers are all about.
It turns out ASP.NET Web API has good support for configuring serializers on the default Json and Xml formatters, but the way you go about it varies depending on which formatter you are using. For example, Json.Net supports setting the serializer to preserve references globally, but the DataContractSerializer requires you configure it on a per-type basis by initializing a new serializer for each type. Protobuf-net, on the other hand, requires that you configure metadata for each type property and add it to a static RuntimeTypeModel. What is needed is a simple consistent API for configuring formatters to handle cyclical references, both on the server and on the client.
To address this need, I created a set of NuGet packages which you can add to a project hosting ASP.NET Web API services: AspnetWebApi2Helpers.Serialization and AspnetWebApi2Helpers.Serialization.Protobuf. Then simply add a few lines of code to the WebApiConfig.Register method in the App_Start folder of your web project.
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Configure formatters to handle cyclical references
config.Formatters.JsonPreserveReferences();
config.Formatters.XmlPreserveReferences();
config.Formatters.ProtobufPreserveReferences(typeof(Category).Assembly);
// Other code elided for clarity ...
}
}
Neither the Json nor Xml formatters require passing types to the XxxPreserveReferences method (the API for the Xml serializer is simplified though the use of a custom media type formatter), but that the Protobuf formatter does need the types to be passed, because of a limitation with the current version of the Protobuf-net formatter. The client-side code is similarly straightforward.
// Configure formatter to handle cyclical references
var jsonFormatter = new JsonMediaTypeFormatter();
jsonFormatter.JsonPreserveReferences();
var xmlFormatter = new CustomXmlMediaTypeFormatter();
var protoFormatter = new ProtoBufFormatter();
protoFormatter.ProtobufPreserveReferences();
// Select a formatter
MediaTypeFormatter formatter = jsonFormatter;
var products = productsResponse.Content.ReadAsAsync<List<Product>>(new[] { formatter }).Result;
Lastly, I’ve created NuGet packages which target ASP.NET MVC 6 for use with ASP.NET 5 (vNext) and Visual Studio 2015. The Json and Xml serialization helpers target both the full and core versions of the CLR that are package-deployable and cloud-friendly. (The MVC 6 Protobuf serialization helper depends on Protobuf-net, which does not currently support CLR Core). Here is a Startup class which includes configuration of MvcOption by using the XxxPreserveReferences extension methods.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Configure formatters to handle cyclical references
services.AddMvc()
.Configure<MvcOptions>(options =>
{
options.JsonPreserveReferences();
options.XmlPreserveReferences();
#if ASPNET50
options.ProtobufPreserveReferences(typeof(Product).Assembly);
#endif
});
services.AddWebApiConventions();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
routes.MapWebApiRoute("DefaultApi", "api/{controller}/{id?}"));
app.UseWelcomePage();
}
}
I’ve uploaded all the serialization helper packages to the NuGet Gallery (at this time the versions are pre-release). To use them, simply all them to your projects using the NuGet Package Manager – just remember to select “Include Prerelease” and search for AspNetWebApi2Helpers.
For ASP.NET MVC 6 (vNext), check “Include Prerelease” and search for AspNetMvc6Helpers.
To download the source code and samples, please visit the GitHub repositories for AspNet WebApi 2 Helpers (Asp.Net 4 / vCurrent) and AspNet Mvc 6 Helpers (Asp.Net 5 / vNext).
Enjoy!
Reference: | Handle Cyclical References with ASP.NET Web API 2 and MVC 6 from our NCG partner Tony Sneed at the Tony Sneed’s Blog blog. |