0
votes

I've looked at all the SO posts on this and had no success. I'm able to call into the OData controller and see the data retrieved, mapped, and packaged-up. The controller method exits and I get a 406 - Not Acceptable. I'm only using the System.Web.OData namespace (OData Version v4.0.30319) and, using Postman, I manually set the Content and Accept headers to 'application/json' with no luck

Maybe I've missed something over the last 2 hours reading everyone's posts on similar problems. Any pointers will be appreciated.

UPDATE: The problem appears to be in the Mapper code (Automapper) as pointed out by Igor below. It looks like there's a commitment to return the database(EF) entity, not a mapped class. With this knowledge I found this SO post, but it doesn't offer a solution: ApiController vs ODataController when exposing DTOs. Are we stuck having to return database entities or can the results be mapped? If so, that's a deal breaker for me.

Controller:

[EnableQuery]
[HttpGet]
public async Task<IHttpActionResult> Get()
    {
        var list = await db.ConfigSets.ToListAsync();
        Mapper.CreateMap<ConfigSet, ConfigSetDTO>();
        var configSetDTOs = Mapper.Map<List<ConfigSet>, List<ConfigSetDTO>>(list);
        return Ok(configSetDTOs); //IT LOOKS GOOD HERE!
    }

WebApiConfig:

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
            config.EnableCors();

            // OData - must be before config.Routes when using a prefix. In this case "api"
            config.MapODataServiceRoute("odata", "api", GetEdmModel(), new DefaultODataBatchHandler(GlobalConfiguration.DefaultServer));
            config.EnsureInitialized();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );          
        }

        private static IEdmModel GetEdmModel()
        {
            var builder = new ODataConventionModelBuilder();
            builder.Namespace = "Services";
            builder.ContainerName = "DefaultContainer";
            builder.EntitySet<ConfigSet>("Configuration");
            var edmModel = builder.GetEdmModel();
            return edmModel;
        }
    }
2
I am not sure what Mapper.Map returns but whatever it is it should implement IQueryable or the generic there of. Have you tried testing it with a hard coded List<ConfigSetDTO> instance and in the return call do return Ok(myList.AsQueryable()); ? That should help you narrow down where the issue is, overall configuration or with the data or format of the data you are trying to return. - Igor
@Igor..Mapper is Automapper. I tried this code without it and what you suggested and get the same result. - Big Daddy
I think I see it. builder.EntitySet<ConfigSet>("Configuration"); denotes that the expected type that should be returned from the Get() method is of type ConfigSet but you are returning type ConfigSetDTO. To see if that is indeed the problem try returning a hard coded list of ConfigSet instances but call AsQueryable() in the Ok method. Ie; return Ok(myConfigSetList.AsQueryable()); - Igor
@Igor...you're onto something. The mapping must be the problem because this works: var list = await db.ConfigSets.ToListAsync();return Ok(list); - Big Daddy
I have never used a Get() function from oData with anything other than EF data that I pull from a SQL view so I am not sure what is or is not possible. The whole purpose, as I understand it, is that the MS Odata Web API controller handles all of the query logic based on the incoming URL which is why you would want to use oData with a Get-er function as it provides a standard way to query the data, no extra code necessary for filtering back end, and filtering occurs on the database and not in memory. If you push something like Automapper between this I doubt it would work the same. - Igor

2 Answers

1
votes

I have never used a Get() function from oData with anything other than EF data that I pull from a SQL view so I am not sure what is or is not possible. The whole purpose, as I understand it, is that the MS Odata Web API controller handles all of the query logic based on the incoming URL which is why you would want to use oData with a Get method as it provides a standard way to query the data, no extra code necessary for filtering back end, and filtering occurs on the database and not in memory. If you push something like Automapper between this I doubt it would work the same.

1
votes

The older versions of OData allow you to just expose any old IQueryable, whether it contains database objects or DTOs.

However, I can definitely say that you are using AutoMapper wrong here - you are doing the mapping within the client, rather than on the database, which would still allow you to return an IQueryable. Instead of using Mapper.Map, use the .ProjectTo<> extension method on your query:

using AutoMapper.QueryableExtensions;  // This using is required

[EnableQuery]
[HttpGet]
public IHttpActionResult Get()
{
    var query = db.ConfigSets.ProjectTo<ConfigSetDTO>();
    return Ok(query);
}

You should also not be defining your maps in action methods - they should be defined once at your application startup.