Skip to content

Can I get OData Query Options to API Explorer with ApiController? #522

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Olli-palkkaus opened this issue Jul 19, 2019 · 3 comments
Closed

Comments

@Olli-palkkaus
Copy link

We have an existing API with about 20 CRUD services each with CRUD + some business logic methods. It is not generally OData (more traditional CRUD API) and we do not want to generally change it much.

However, we would like to add $filter, paging ($top, $skip, pageSize), $count, $order and perhaps $select to our 20-30 Get / List methods that now have usecase specific filtering by property (e.g. .../cms/articles?type=blogPost).

We can add these features very nicely using aspnet-api-versioning (thanks for that)...


// In Config:
var constraintResolver = new DefaultInlineConstraintResolver() {
  ConstraintMap = { ["apiVersion"] = typeof(ApiVersionRouteConstraint) } };
config.MapHttpAttributeRoutes(constraintResolver);

// In Controller:
[ApiVersion("2")]
[RoutePrefix("v0{version:apiVersion}/api/cms/articles")]
public class CmsController : ApiController

// New v3.0 method with OData support: Works, but $top etc. do not show in Swagger
[Route]
[HttpGet]
[SwaggerResponse(HttpStatusCode.OK, type: typeof(Article[]))]
[ResponseType(typeof(ODataValue<IEnumerable<Article>>))]
[EnableQuery(MaxTop = 100, AllowedQueryOptions = Select | Top | Skip | Count)]
[ApiVersion("3.0-rc")]
public PageResult<Article> ContentQueryable(ODataQueryOptions<Article> options)

// Non-OData method for existing v2.0. This needs to be kept as is.
[Route]
[HttpGet]
[SwaggerResponse(HttpStatusCode.OK, type: typeof(Article[]))]
public HttpResponseMessage CrudList([FromUri] AbcSection[] filter = null)

This works fine, but I am not able to get the OData Query options to show in Swagger. All the examples use ODataController and ODataRouting.

The question is: Do I need to move away from ApiController to ODataController AND/OR from MapHttpAttributeRoutes to MapVersionedODataRoutes to get the OData query options showing in Swagger?

Or can I use some option or extension point to get them showing with the code above? I do not really understand what triggers the generation of these Options to Swagger.

@Olli-palkkaus Olli-palkkaus changed the title Can I use get OData Query Options to API Explorer with ApiController? Can I get OData Query Options to API Explorer with ApiController? Jul 19, 2019
@commonsensesoftware commonsensesoftware self-assigned this Jul 19, 2019
@commonsensesoftware
Copy link
Collaborator

You do not need to use ODataController because you might have your own base class (I've found uses for this in the past); however, ODataRouting is required out-of-the-box.

Query options are derived from the following pieces of information:

  1. Globally configured query options
  2. Declarative query options using [EnableQuery]
  3. Declarative (attribute) or imperative (via the fluent interface API) Model Bound query options
  4. Imperative query option conventions (via the API Explorer)

2 and 4 do not really need to be OData-specific, but it makes sense for there to be an affinity out-of-the-box.

Your scenario is completely valid, though I've only ever seen it in practice one other time. I haven't tried this myself, but let me walk you through the touch points which should get things working. Ultimately, I think you can only make this work by way of conventions.

Extend the Default Query Options Builder

First, you need to extend the ODataQueryOptionsConventionBuilder, override the ApplyTo method and remove the check that filters ApiDescription instances down to OData actions.

Extend the Versioned API Explorer

Next, you'll need to extend VersionedApiExplorer. You'll pass in your OData API Explorer options configured with your custom query options builder. Override the InitializeApiDescriptions method and apply the configured OData API explorer options similar to the ODataApiExplorer.ExploreQueryOptions method.

This would look something like:

public class HybridApiExplorer : VersionedApiExplorer
{
    readonly ODataApiExplorerOptions odata;

    public HybridApiExplorer(
        HttpConfiguration configuration,
        ODataApiExplorerOptions options )
        : base( configuration, options ) => odata = options;

    protected override ApiDescriptionGroupCollection InitializeApiDescriptions()
    {
        var groups = base.InitializeApiDescriptions();
        var queryOptions = odata.QueryOptions;
        var settings = new ODataQueryOptionSettings()
        {
            DescriptionProvider = queryOptions.DescriptionProvider,
        };

        foreach ( var group in groups )
        {
            queryOptions.ApplyTo( group.ApiDescriptions, settings );
        }

        return groups;
    }
}

Configure Your Application

Finally, you'll have to configure your application with all of the pieces. It would look something like:

var options = new ODataApiExplorerOptions( configuration )
{
    QueryOptions = new MyCustomODataQueryOptionsBuilder(),
};

options.QueryOptions
       .Controller<CmsController>()
       .Action( c => c.ContentQueryable( default ) );

// TODO: other configurations

var apiExplorer = new HybridApiExplorer( configuration, options );

configuration.Services.Replace( typeof( IApiExplorer ), apiExplorer );

Disclaimer

This is how things should work - in theory. Again, I haven't tried this myself. The default conventions have some assumptions about available OData information such as the corresponding EDM that will not be configured using this approach. There may be additional customization required to achieve the desired result.

I'm not sure how common this scenario actually is. If there's some community demand for it, I'd consider evaluating whether this type of scenario can be supported out-of-the-box without so much customization.

I hope that helps get you started.

@Olli-palkkaus
Copy link
Author

Great, thanks a lot for a quick and what looks like a through answer!

I will try this approach. Not sure if I will have time in the beginning of next week or then maybe later in August. I will give feedback on how it went and perhaps contribute a demo project.

@commonsensesoftware
Copy link
Collaborator

Do you happen to have the world's simplest sample application of the desired configuration? I was going to take a look at intrinsically supporting this out-of-the-box, but I want to make sure I use the same setup and configuration you've used to ensure it works.

No rush, but it would certainly accelerate the process of looking into what it would take to support it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants