3

If I have a Customer class that just has simple properties (e.g. Name, etc.) then I can create a CustomersController that derives from ApiController, and query my Customer objects using the URL /api/customers.

Similarly, if I have an Order class with simple properties (Description, Amount) then I can create an OrdersController and access it in the same way.

However, if I then add a property to my Customer class that lists the Orders associated with that Customer:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    // ...
    public virtual ICollection<Order> Orders { get; set; }
}

...then when I try to query using /api/customers I get a 500 Server Error. I can put a breakpoint within the CustomersController and can see that it does call the correct method, and attempts to return a value. However, somewhere in the guts of the framework it fails to turn that into an HTTP response.

It looks like it cannot serialize the Orders information and/or package it up for the HTTP response, but I'm not sure what I need to do to make this happen. Or is it simply the case that the WebApi stuff is not designed to return complex objects?


Update: my code is here: https://gist.github.com/43ed2f29f8adfb44cef6


Update 2: more information on the error I get:

Server error
The website encountered an error while retrieving http://localhost:1031/api/customers/6.

It may be down for maintenance or configured incorrectly. Here are some suggestions: Reload this webpage later. HTTP Error 500 (Internal Server Error): An unexpected condition was encountered while the server was attempting to fulfill the request.

The Intellitrace log shows the following: https://i.stack.imgur.com/8R1XL.png

[I can hardly believe that the Intellitrace window does not allow me to copy the text, so that I had to actually print-screen and upload an image. What year is this?]


Related: ASP.Net Web API showing correctly in VS but giving HTTP500

Community
  • 1
  • 1
Gary McGill
  • 26,400
  • 25
  • 118
  • 202

2 Answers2

7

You are passing an Entity Framework entity which will cause problems. You need Data Transfer Objects to pass and receive your data. Because you bind entities together, it won't allow you to serialize the data.

tugberk
  • 57,477
  • 67
  • 243
  • 335
  • Thanks. Sounds good (though I am of course disappointed that it doesn't "just work"). I guess I'll google DTO. – Gary McGill Jul 22 '12 at 23:02
  • @GaryMcGill here, I had the same problem but in SPA which is not available now but the underlying layer is the web api: http://forums.asp.net/t/1773164.aspx/1?DbContext+Object+graph+for+type+contains+cycles+and+cannot+be+serialized+if+reference+tracking+is+disabled – tugberk Jul 22 '12 at 23:14
  • 1
    Thanks for your help. You are correct that it's the serialization that's the problem. What I had not realised is that it attempts to serialize the EF proxy class for Presentation rather than just my POCO class. However, rather than create a whole bunch of new classes to return from my controller, I discovered that I could simply turn off proxy generation in my DbContext, and thereafter everything worked as I wanted. See my answer. – Gary McGill Jul 23 '12 at 09:26
  • @GaryMcGill that would work but it is not a best practice to send your entire db table fields over the wire. The best approach here is DTOs. – tugberk Jul 23 '12 at 09:50
  • I should have listened. Having persevered with my "hack" to get the POCO classes to work, I ran into more issues down the line. I've now switched to DTOs. It wasn't until I came across AutoMapper that I was prepared to go down the DTO route. That makes it much less painful. – Gary McGill Aug 01 '12 at 22:27
3

OK, apologies for answering my own question, especially after all the help I've received (thanks tugberk), but there was a simple(r) solution.

As tugberk pointed out, the problem was that I was using Entity Framework code-first to work with my POCO objects. When the framework attempted to serialize my objects, it was serializing the EF proxy classes rather than just the POCO objects.

Tugberk suggested using DTO to pass and receive the data (essentially creating new classes for this purpose), but I really didn't want to do that, since my POCO classes looked perfectly adequate to me, and having duplicate classes does not seem very DRY.

I then discovered (via https://stackoverflow.com/a/11386206/98422) that I could turn off proxy generation on the DbContext in my controller (as below), and this would cause the POCO objects to be serialized rather than the proxy classes - and thus avoid the 500 error:

db.Configuration.ProxyCreationEnabled = false;

That was only 50% of the solution, though, because although this avoided the error, it did not include any data for the child objects. (Those came through in the XML as nil). That's because my POCO classes are set up for lazy loading via EF. To get around that, I had to explicitly load the child objects:

return db.Customers.Include("Orders").AsEnumerable();

Job done, and thankfully not much code to write.

Community
  • 1
  • 1
Gary McGill
  • 26,400
  • 25
  • 118
  • 202