Search code examples
c#.netasp.net-web-apiodatahttp-status-code-406

ODataController returning HTTP 406 Not Acceptable


I'm building an OData 3 service on Web API 2.2.

The service is correctly returning the metadata for my entities, but returns 406 Not Available when I query one of the actual entities. I've done quite a bit of research (I'm currently following several tutorials), but I haven't found anything that actually works.

Here's my WebApiConfig:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Http;
using System.Web.OData.Builder;
using System.Web.OData.Extensions;

namespace MyProject
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            ODataModelBuilder builder = new ODataConventionModelBuilder();

            builder.EntitySet<MarvelCharacter>("MarvelCharacters");
            config.MapODataServiceRoute(
                routeName: "Marvel",
                routePrefix: "dude",
                model: builder.GetEdmModel());
        }
    }
}

And my controller (not complete, but you get the idea):

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.OData;
using System.Web.Http.OData.Query;
using Microsoft.Data.OData;
using MyProject;

namespace MyProject.Controllers
{
    public class MarvelCharactersController : ODataController
    {
        private static ODataValidationSettings _validationSettings = new ODataValidationSettings();

        // GET: odata/MarvelCharacters
        public IHttpActionResult GetMarvelCharacters(ODataQueryOptions<MarvelCharacter> queryOptions)
        {
            // validate the query.
            try
            {
                queryOptions.Validate(_validationSettings);
            }
            catch (ODataException ex)
            {
                return BadRequest(ex.Message);
            }

            var entities = new myEntities();
            var marvelCharacters = (from c in entities.MarvelCharacters select c).ToList();

            return Ok<IEnumerable<MarvelCharacter>>(marvelCharacters);
        }
    }
}

Solution

  • Turns out the answer to this one was pretty simple, but not covered well by any documentation I could find.

    I was trying to implement an OData 3 endpoint on WebAPI 2.2. I was following several different tutorials, some for OData 3 and some for OData 4.

    I was using OData 4 (System.Web.OData) in my WebApiConfig and OData 3 (System.Web.Http.OData) in my controller. Turns out, they don't play well together.

    I decided to post the answer here in case anyone else has a similar issue.

    To add a little value, and since I was mixing both anyway, I decided to go ahead and setup support for both version 3 and 4 by aliasing the namespaces in my WebApiConfig and then creating versioned controllers.

    WebApiConfig:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.Http;
    using ODataV3 = System.Web.Http.OData;
    using ODataV4 = System.Web.OData;
    
    namespace MyProject
    {
        public static class WebApiConfig
        {
            public static void Register(HttpConfiguration config)
            {
                // OData V3 Route
    
                ODataV3.Builder.ODataModelBuilder builder3 = new ODataV3.Builder.ODataConventionModelBuilder();
    
                builder3.EntitySet<MarvelCharacter>("MarvelCharactersV3");
                // The MapODataRoute function is deprecated in WebAPI 2.2,
                // but I haven't found an alternative for supporting OData 3.
                config.Routes.MapODataRoute(
                    routeName: "Marvel3",
                    routePrefix: "dude3",
                    model: builder3.GetEdmModel());
    
                // ODate V4 Route
    
                ODataV4.Builder.ODataModelBuilder builder4 = new ODataV4.Builder.ODataConventionModelBuilder();
    
                builder4.EntitySet<MarvelCharacter>("MarvelCharactersV4");
                ODataV4.Extensions.HttpConfigurationExtensions.MapODataServiceRoute(
                    configuration: config,
                    routeName: "Marvel4",
                    routePrefix: "dude4",
                    model: builder4.GetEdmModel());
            }
        }
    }
    

    MarvelCharactersV3 (OData 3):

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using System.Web.Http.OData; // OData V3
    using System.Web.Http.OData.Query;
    using Microsoft.Data.OData;
    using MyProject;
    
    namespace MyProject.Controllers
    {
        public class MarvelCharactersV3Controller : ODataController
        {
            private static ODataValidationSettings _validationSettings = new ODataValidationSettings();
    
            // GET: odata/MarvelCharacters
            public IHttpActionResult GetMarvelCharactersV3(ODataQueryOptions<MarvelCharacter> queryOptions)
            {
                // validate the query.
                try
                {
                    queryOptions.Validate(_validationSettings);
                }
                catch (ODataException ex)
                {
                    return BadRequest(ex.Message);
                }
    
                var entities = new myEntities();
                var marvelCharacters = (from c in entities.MarvelCharacters select c).ToList();
    
                return Ok<IEnumerable<MarvelCharacter>>(marvelCharacters);
            }
        }
    }
    

    MarvelCharactersV4 (OData 4):

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using System.Web.OData; // OData 4
    using System.Web.OData.Query;
    using Microsoft.Data.OData;
    using MyProject;
    
    namespace MyProject.Controllers
    {
        public class MarvelCharactersV4Controller : ODataController
        {
            private static ODataValidationSettings _validationSettings = new ODataValidationSettings();
    
            // GET: odata/MarvelCharacters
            public IHttpActionResult GetMarvelCharactersV4(ODataQueryOptions<MarvelCharacter> queryOptions)
            {
                // validate the query.
                try
                {
                    queryOptions.Validate(_validationSettings);
                }
                catch (ODataException ex)
                {
                    return BadRequest(ex.Message);
                }
    
                var entities = new myEntities();
                var marvelCharacters = (from c in entities.MarvelCharacters select c).ToList();
    
                return Ok<IEnumerable<MarvelCharacter>>(marvelCharacters);
            }
        }
    }
    

    It's probably not the best architecture (I will probably create a library to consolidate similar code between the controllers), but I've tested and I can successfully query via OData 3 and OData 4, so I'm happy enough with it for now.