Search code examples
asp.net-web-apiodatadatajs

Asp.Net WebApi OData with DataJs throws an error


I just created a really simple ASP.Net WebApi project. I used NuGet to download the latest OData in WebAPI – RC release. I also download DataJs and Knockout via NuGet. All my dependencies are up to date. I created a simple "Books" class and wired everything together using HttpConfiguration.EnableOData(IEdmModel). I also added the [Queryable] attribute to my Get action in the controller. There is not database involved, I hard-coded the data I want returned. Basically, I did the minimum amount of changes to run my project with WebApi and OData.

When I try to query the OData service using DataJs, I get a 500 Internal Server Error in the response, but if I browse to the URL directly I can see the XML data. I've included the request, response, my C# class, the Javascript code, and the Global.asax code. What am I missing to get this to work?

REQUEST

Response Headers
Cache-Control   private
Content-Length  966
Content-Type    application/json; odata=fullmetadata; charset=utf-8
DataServiceVersion  3.0;
Date    Fri, 21 Dec 2012 22:13:27 GMT
Server  Microsoft-IIS/8.0
X-AspNet-Version    4.0.30319
X-Powered-By    ASP.NET
X-SourceFiles   =?UTF-8?B?YzpcdXNlcnNcanVzdGluXGRvY3VtZW50c1x2aXN1YWwgc3R1ZGlvIDIwMTJcUHJvamVjdHNcRGF0YUpzU3Bpa2VcRGF0YUpzU3Bpa2VcYXBpXEJvb2tz?=
Request Headers
Accept  application/atomsvc+xml;q=0.8, application/json;odata=fullmetadata;q=0.7, application/json;q=0.5, */*;q=0.1
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Connection  keep-alive
Cookie  glimpseState=null; glimpseLatestVersion=0.87; glimpseOptions=null; glimpseClientName=null
Host    localhost:31652
MaxDataServiceVersion   3.0
Referer http://{localhost}/
User-Agent  Mozilla/5.0 (Windows NT 6.2; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0

RESPONSE

{
  "odata.error":{
    "code":"","message":{
      "lang":"en-US","value":"An error has occurred."
    },"innererror":{
      "message":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; odata=fullmetadata; charset=utf-8'.","type":"System.InvalidOperationException","stacktrace":"","internalexception":{
        "message":"The related entity set could not be found. The related entity set is required to serialize the payload.","type":"System.Runtime.Serialization.SerializationException","stacktrace":"   at System.Web.Http.OData.Formatter.Serialization.ODataFeedSerializer.WriteObject(Object graph, ODataMessageWriter messageWriter, ODataSerializerContext writeContext)\r\n   at System.Web.Http.OData.Formatter.ODataMediaTypeFormatter.<>c__DisplayClass8.<WriteToStreamAsync>b__7()\r\n   at System.Threading.Tasks.TaskHelpers.RunSynchronously(Action action, CancellationToken token)"
      }
    }
  }
}

C# Class

namespace DataJsSpike.Models
{
    public class Book
    {
        public string ISBN { get; set; }

        public string Title { get; set; }

        public string Author { get; set; }

        public string Publisher { get; set; }
    }
}

Javascript Code

// the URL of the first page to retrieve
var startPage = "api/Books";

var viewModel = new Object();
viewModel.books = ko.observable();

// On initialization, make a request for the first page
$(document).ready(function () {
    LoadDataJs();

    function LoadDataJs() {
        OData.read(startPage, function (data) {
            viewModel.books(data.results);
            ko.applyBindings(viewModel);
        });
    }
});

Global.asax

public class WebApiApplication : HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();

        WebApiConfig.Register(GlobalConfiguration.Configuration);
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);

        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);

        var modelBuilder = new ODataConventionModelBuilder();
        EntityTypeConfiguration<Book> bookConfiguration = modelBuilder.Entity<Book>();

        bookConfiguration.HasKey(x => x.ISBN);
        modelBuilder.EntitySet<Book>("Books");

        IEdmModel model = modelBuilder.GetEdmModel();

        GlobalConfiguration.Configuration.EnableOData(model, "api");
    }
}

Solution

  • EnableOData actually registers a route for you, but since you registered routes before it ran, those routes take precedence. If you remove this line:

    RouteConfig.RegisterRoutes(RouteTable.Routes);
    

    I think it should work out. The request needs to come in on an OData route for the OData formatting to work because the route parses the OData path and gives the formatter information about things like the Entity Set that's being accessed.