10
votes

I have a Web API project which has been used for several years without OData support, just with standard URL params.

I now wish to add OData support to this API, but as the API is not built on a queryable model the intention is to receive the ODataQueryOptions<T> object and pass this down to a repository.

Everything I can find to read about supporting OData either assumes that I have a queryable model or is overly simplistic and simply tells me how to make sense of the ODataQueryOptions object. Consequently I'm unable to get a simple method up and running.

Here's what I currently have.

[Route("test")]
[HttpGet]
[EnableQuery]
public IHttpActionResult Test(ODataQueryOptions<TestOptions> options)
{
    var settings = new ODataValidationSettings {
            AllowedFunctions = AllowedFunctions.None,
            AllowedLogicalOperators = AllowedLogicalOperators.Equal,
            AllowedArithmeticOperators = AllowedArithmeticOperators.None,
            AllowedQueryOptions = AllowedQueryOptions.Filter
        };
    try
    {
        options.Validate(settings);
    }
    catch (ODataException exception)
    {
        return BadRequest(exception.Message);
    }

    var binaryOperator = options.Filter?.FilterClause?.Expression as BinaryOperatorNode;
    if (binaryOperator != null)
    {
        var property = binaryOperator.Left as SingleValuePropertyAccessNode ?? binaryOperator.Right as SingleValuePropertyAccessNode;
        var constant = binaryOperator.Left as ConstantNode ?? binaryOperator.Right as ConstantNode;

        if (property?.Property != null && constant?.Value != null)
        {
            ;
        }
    }

    return Ok();
}

The TestOptions class (in the ODataQueryOptions<TestOptions> param) is currently an empty class:

public class TestOptions
{
}

I've also added

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // existing code

        config.AddODataQueryFilter();
    }
}

However, upon calling this from a REST API client...

{
"Message": "An error has occurred.",
"ExceptionMessage": "No non-OData HTTP route registered.",
"ExceptionType": "System.InvalidOperationException",
"StackTrace": " ... "
}

What have I missed? I would have thought that I might have to register OData-enabled methods in the global.asax or similar, but the exception implies that the problem is with non-OData methods, but all the other methods still return as expected (i.e., without any OData involvement).

2
I think that the error message is somewhat misleading here and that you need to register the OData route. Following the instructions here should get you started: docs.microsoft.com/en-us/aspnet/web-api/overview/… - TomDoesCode
That example is built upon an EF model and like I said in the OP, I don't have an underlying model. The example registers the OData endpoint with specific details of its model: docs.microsoft.com/en-us/aspnet/web-api/overview/… - awj
OK, you still need to build a model though, even if it isn't through entity framework. Will your model be defined at compile time? I.E will the types and properties that are exposed change? - TomDoesCode
I don't think those objects and properties will change after compile time, but what I'm looking to achieve is a public interface which meets OData standards but without an underlying model; I simply want the OData library to parse the request and pass a populated ODataQueryOptions object detailing the properties, conditions, sorting, paging, etc params. Is this not possible? This suggests it is possible: ben-morris.com/… - awj
That article is suggesting that you don't need to expose IQueryable which is correct but you still need to define an OData model. In that article, it is the Product class that is the basis for the OData model. For that example, you would need to add the Product class to the ODataModelBuilder. In the first link I sent, look at the "Configure the OData Endpoint" section, I think that is the missing link here - TomDoesCode

2 Answers

8
votes

Actually, this works perfectly without EntityDate or any other model setup. You just need a List<Poco.Language> that you can convert with .AsQueryable() and off you go.

[Route(""), HttpGet]
public IHttpActionResult  Get(ODataQueryOptions<Poco.Language> queryOptions)
{          
    return Ok(queryOptions.ApplyTo(_repository.GetAll().AsQueryable()));
}

Above controller can be called with all types of OData query options, normal routes and no setup in the WebApiConfig.

Poco.Language is just a plain C# POCO class.

0
votes

Did you add this to your Startup?

public void Configuration(IAppBuilder appBuilder)
        {
            // Set up server configuration
            var config = new HttpConfiguration();
            config.Routes.MapODataRoute(routeName: "OData", routePrefix: "odata", model: GetEdmModel());
            appBuilder.UseWebApi(config);
        }

        private IEdmModel GetEdmModel()
        {
            var modelBuilder = new ODataConventionModelBuilder();
            modelBuilder.EntitySet<Customer>("customer");
            modelBuilder.EntitySet<Order>("order");
            modelBuilder.EntitySet<Customer>("response");
            return modelBuilder.GetEdmModel();
        }

I am trying to do the same thing you are, but with a .Net Core Web Api. I downloaded the samples from https://github.com/OData/ODataSamples and he has a working project that does exactly what we're trying to do. Look at ODataQueryableSample.csproj. It doesn't use EntityFramework either, just creates a list in memory.

Also, I don't think you need both the [EnableQuery] attribute and ODataQueryOptions parameter - the sample gives an example for both and doesn't use them together.

I haven't gotten this working with my project either, but I have seen it working on my PC so I believe its just a configuration thing (for instance, my Startup uses IApplicationBuilder instead of IAppBuilder). You might also try to hit it from a browser to get a better exception message.