4

after a lot of help yesterday, I came up against a known error in asp.net4 beta - I upgraded to VS2012 RC Express (4.5), and now I'm getting an internal server error, and I can't see why. I'm creating a web API:

Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations.Schema;

namespace MvcApplication6.Models
{
    public class tblCustomerBooking
    {
        [Key()]
        public int customer_id { get; set; }
        public string customer_name { get; set; }
        public string customer_email { get; set; }
        public virtual ICollection<tblRental> tblRentals { get; set; }
    }

    public class tblRental
    {
        [Key()]
        public int rental_id { get; set; }
        public int room_id { get; set; }
        public DateTime check_in { get; set; }
        public DateTime check_out { get; set; }
        public decimal room_cost { get; set; }
        public int customer_id { get; set; }
        [ForeignKey("customer_id")]
        public virtual tblCustomerBooking tblCustomerBooking { get; set; }
    }
}

I then used the Add Controller wizard, selected "Template: API controller with read/write actoins, using Entity Framework", chose tblCustomerBooking as my Model Class, and clicked , which is:

using System.Data.Entity;

namespace MvcApplication6.Models
{
    public class BookingsContext : DbContext
    {
        public BookingsContext() : base("name=BookingsContext")
        {
        }
        public DbSet<tblCustomerBooking> tblCustomerBookings { get; set; }
    }
}

My Controller (BookingsController.cs) automatically generated by Visual Studio 2012 Express is:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using MvcApplication6.Models;

namespace MvcApplication6.Controllers
{
    public class BookingsController : ApiController
    {
        private BookingsContext db = new BookingsContext();

        // GET api/Bookings
        public IEnumerable<tblCustomerBooking> GettblCustomerBookings()
        {
            return db.tblCustomerBookings.AsEnumerable();
        }
    }
}

I added a breakpoint at "return db....." above, and checked the Watch part in VS - it clearly shows the object, with the customer, and the associated rentals:

Snapshot of customer and rental objects

However if I allow the script to continue, I just get an http500 error (as shown in Fiddler below): Fiddler screenshot showing HTTP500

Is there any more code I can add into the controller to allow me to see why it is erroring? Or can anyone see what may be wrong? VS appears to retrieve it ok, as shown in the first screenshot, but doesn't seem to be able to send it out.

Thanks for any help or pointers,

Mark

Update

Hi - am I simply asking too much of the API? Is it not possible (out of the box) for it to simply return objects with one to many relationships? Can it only really produce a single object list?

Thanks, Mark

Community
  • 1
  • 1
Mark
  • 7,778
  • 24
  • 89
  • 147

7 Answers7

2

[Update]

1, change the action code to include the navigation property data:

    // GET api/Bookings
    public IEnumerable<tblCustomerBooking> GettblCustomerBookings()
    {
        return db.tblCustomerBookings.Include("tblRentals").AsEnumerable();
    }

2, turn off proxy in data context

EF suggests to turn off proxy when serializing POCO.

If you want to support proxy, there are different ways for different serializers:

JSON.net serializer: It can support proxy by upgrading to latest version. Version 4.5.1 has a bug that can't support ignore NonserializedAttribute. It will block proxy to be serialized.

DataContractSerializer (JSON/XML): use ProxyDataContractResolver to resolve the types, here is a walkthrough.

3, enable preserving reference in model class

Both json.net and DataContract serializer support detecting circular reference and they give developer to control how to handle it.

Change the model class to following:

[JsonObject(IsReference = true)]
[DataContract(IsReference = true)]
public class tblCustomerBooking
{
    [Key()]
    public int customer_id { get; set; }
    [DataMember]
    public string customer_name { get; set; }
    [DataMember]
    public string customer_email { get; set; }
    [DataMember]
    public virtual ICollection<tblRental> tblRentals { get; set; }
}


public class tblRental
{
    [Key()]
    public int rental_id { get; set; }
    public int room_id { get; set; }
    public DateTime check_in { get; set; }
    public DateTime check_out { get; set; }
    public decimal room_cost { get; set; }
    public int customer_id { get; set; }
    [ForeignKey("customer_id")]
    public virtual tblCustomerBooking tblCustomerBooking { get; set; }
}

Note that, if the model is attributed with DataContract, you have to specify DataMember for all its members, otherwise none of them will be serialized.

Hongye Sun
  • 3,868
  • 1
  • 25
  • 18
2

To resolve the error 500 issue when returning JSON with entities with virtual keyword I did the following,

    public class BookingsController : ApiController
{
    private BookingsContext db = new BookingsContext();

    // GET api/Bookings
    public IEnumerable<tblCustomerBooking> GettblCustomerBookings()
    {
        db.Configuration.ProxyCreationEnabled = false;  
        return db.tblCustomerBookings.AsEnumerable();
    }
}

It's enough to disable proxy creation (which disables lazy loading as well) for the specific circumstances where proxies are disturbing, like serialization. This disables proxy creation only for the specific context instance of db

db.Configuration.ProxyCreationEnabled = false;

http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx

http://blogs.msdn.com/b/adonet/archive/2011/01/27/using-dbcontext-in-ef-feature-ctp5-part-1-introduction-and-model.aspx

Diganta Kumar
  • 3,637
  • 3
  • 27
  • 29
1

You might want to add a global error handler to your project. It can trap and log any odd errors that are happening in background threads. This S/O article talks about some solid approaches. They will save you lots of time in any project: ASP.NET MVC Error Logging in Both Global.asax and Error.aspx

Community
  • 1
  • 1
tgolisch
  • 6,549
  • 3
  • 24
  • 42
  • I've used ELMAH in webforms, but so far, haven't been able to get it to work with the RC API (there's a lot of conflicting advice). – Mark Jun 05 '12 at 14:05
1

are you doing:

db.tblCustomerBookings.Include("tblRentals").Select(i => 
    new { i.something //etc });

also, which MediaTypeFormatter are you using, Xml or Json? Error 500 often means that Formatter is choking.

Switch to JSON.NET formatter (in Web API RC the easiest way is to do GlobalConfiguration.Configuration.Formatters.RemoveAt(1) - this removes XML formatter) and see if it helps or at least gives more meaningful error (or request your method with content type JSON).

Filip W
  • 27,097
  • 6
  • 95
  • 82
  • if you get 'System.Collections.Generic.List' to 'System.Collections.Generic.IList'. An explicit conversion exists (are you missing a cast?) that's when you need "dynamic" to the rescue - instead of IList as returned type – Filip W Jun 05 '12 at 14:58
  • Hi @Filip W - thank you - yes, I tried that method - but VS wouldn't compile/run, as it didn't recognise the { i.rental_id} - just got the red line under it, saying I'm missing a directive or reference. In fiddler, I tried requesting both XML and JSON using: User-Agent: Fiddler Host: localhost:65387 Accept: application/json (and /xml for xml) - I also got an error advising GlobalConfiguration doesn't contain a definition for 'Formatters' - thanks again. – Mark Jun 05 '12 at 15:05
  • since tblRentals is a virtual property you need to explicitly load it, hence the Include above, did you do it like that? – Filip W Jun 05 '12 at 15:12
  • Hi @Filip W - yes I did: Public dynamic GettblCustomerBookings() { return db.tblCustomerBookings.Include("tblRentals").Select(i => new { i.rental_id }); } - rental_id is a property on the tblRentals model, but VS won't run, but gives the error: 'MvcApplication6.Models.tblCustomerBooking' does not contain a definition for 'rental_id' and no extension method 'rental_id' accepting a first argument of type 'MvcApplication6.Models.tblCustomerBooking' could be found (are you missing a using directive or an assembly reference?) - thanks again. – Mark Jun 05 '12 at 15:18
  • try: public dynamic GettblCustomerBookings() { db.Configuration.LazyLoadingEnabled = false; return db.tblCustomerBookings.Include("tblRentals").Select(i => new { name = i.customer_id, rentals = i.tblRentals.ToArray() }).ToList(); } – Filip W Jun 05 '12 at 15:32
  • Hi @Filip-W - that compiled ok, but I got an error: LINQ to Entities does not recognize the method 'MvcApplication6.Models.tblRental[] ToArray[tblRental](System.Collections.Generic.IEnumerable`1[MvcApplication6.Models.tblRental])' method, and this method cannot be translated into a store expression. The exact method is as you typed: public dynamic GettblCustomerBookings() { db.Configuration.LazyLoadingEnabled = false; return db.tblCustomerBookings.Include("tblRentals").Select(i => new { name = i.customer_id, rentals = i.tblRentals.ToArray() }).ToList(); } – Mark Jun 05 '12 at 15:42
  • If I take the 'virtual' part out of the models, then it will return just the tblCustomers table - but I need it to return tblCustomers and any associated records in tblRentals. Is this just something that the Web API can't do do you think? – Mark Jun 05 '12 at 15:50
  • thank you for all of your help - I've managed to get a parent/child return from the api, through your suggestions, and trial and error, and a little more help too. Cheers, Mark – Mark Jun 07 '12 at 08:31
  • I am having the same problem, what did you do to fix it? – jcreamer898 Jun 27 '12 at 03:38
0

I guess you are getting an exception at serialization due to lazy loading of the entities.

This thread may help you.

UPDATE:

Try if this working or not and if yes then the issue is mostly what I said

public IList<tblCustomerBooking> GettblCustomerBookings()
{
    var custBookings = db.tblCustomerBookings.Include("tblRentals").AsEnumerable();

    return custBookings
               .Select(c => new tblCustomerBooking
                            { 
                               customer_id = c.customer_id,
                               customer_name = c.customer_name,
                               customer_email = c.customer_email,
                               tblRentals = c.tblRentals
                                               .Select(r => new tblRentals
                                                      {
                                                          rental_id = r.rental_id,
                                                          // other props exclude the 
                                                          // tblCustomerBooking 
                                                      })
                            }
                      ).ToList();
}

I guess if you are using JSON.NET library with web api you can easily control the properties that don't need to be serialized by specifying the [JsonIgnore] attribute and there by you can avoid writing the above LINQ query.

http://code.msdn.microsoft.com/Using-JSONNET-with-ASPNET-b2423706

http://james.newtonking.com/archive/2009/10/23/efficient-json-with-json-net-reducing-serialized-json-size.aspx

VJAI
  • 32,167
  • 23
  • 102
  • 164
  • Hi - @Mark - I tried that link: http://www.strathweb.com/2012/03/serializing-entity-framework-objects-to-json-in-asp-net-web-api/ - however VS won't compile, as it doesn't recognise (eg.) i.rental_id (is that type safety)? – Mark Jun 05 '12 at 14:04
  • Hi @Mark thanks for the suggestion - VS reports an error: Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IList'. An explicit conversion exists (are you missing a cast?) – Mark Jun 05 '12 at 14:54
  • Hi @Mark - that works, if I convert it to Dynamic - but is still only returning the tblCustomerBookings info - can your code be adapted to add on the associated tblRentals information to the JSON? – Mark Jun 05 '12 at 15:12
  • You don't need to specify 'dynamic' if you specify the type name after "new" keyword. – VJAI Jun 06 '12 at 10:12
0

Api Controller is based on Convention-Over-Configuration so you can try to solve in either one of the following ways:

S P
  • 4,615
  • 2
  • 18
  • 31
  • I tried that too (changed to Get), but still got no more information through Fiddler. – Mark Jun 05 '12 at 14:04
0

I struggled with this issue of serializing the proxy object to a poco as well. You can build into your context a flag that switches the db.Configuration.ProxyCreationEnabled = false;

Or you should set up a viewmodel and take the proxy object and assign the parameters to the viewmodel.

public IEnumerable<tblCustomerBooking> GettblCustomerBookings()
        {
            return db.tblCustomerBookings.Select(cb=> new CustomerBookingsViewModel{id=cb.Id, et.....);
        }

or use an anonymous type:

.Select(cb=>new{id=cb.id....}
Mike
  • 217
  • 4
  • 16