Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How use OData filters with swagger in an asp.net core WebApi project?

I have an web API written using C# on the top of ASP.NET 5/core with EntityFrameworkCore.

I am using OData to apply filters on when querying the database.

I need to manually apply the ODataQueryOptions to my DbSet<> to generate paged info.

Here is my code

[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    private readonly DbContext _db;

    public ProductsController(DbContext db)
    {
        _db = db;
    }

    [HttpGet]
    [EnableQuery(AllowedQueryOptions = AllowedQueryOptions.All, AllowedFunctions = AllowedFunctions.AllFunctions, MaxTop = 500)]
    public PagedProduct Search(ODataQueryOptions<Product> queryOptions)
    {
        // create a query for that would count the total rows
        var countQuery = queryOptions.ApplyTo(Repository.Query(),  AllowedQueryOptions.Top | AllowedQueryOptions.Skip) as IQueryable<Product>;

        // create a query to pull the data
        var query = queryOptions.ApplyTo(Repository.Query()) as IQueryable<Product>;

        var result = new PagedProduct()
        {
            CurrentPage = 1, // TODO get the current page value from the queryOptions
            PageSize = 100,  // TODO get the page size value from the queryOptions
        };

        result.TotalRecords = await countQuery.Count();
        result.Data = await query.ToList();

        return result;
    }
}

But the above code cause Swagger UI to fail. I get the following error

Fetch error undefined /swagger/v1/swagger.json

That error is caused by using ODataQueryOptions as parameter into the Search action.

How can I get the Swagger UI to work with ODataQueryOptions?

like image 338
Jay Avatar asked Oct 18 '25 07:10

Jay


1 Answers

I ran into similar problem few years ago, so maybe my solution will be useful to you.

I used "native" parameters instead of ODataQueryOptions in my controller:

    [Route("", Name = "GetDocuments")]
    [HttpGet]
    [Produces(typeof(PageDto<DocumentShortDto>))]
    public async Task<IActionResult> GetDocumentsAsync(
        [FromQuery(Name = "$top")] int top,
        [FromQuery(Name = "$skip")] int skip,
        [FromQuery(Name = "$orderby")] string orderby,
        [FromQuery(Name = "$filter")] string filter)

Then I created ODataQueryOptions with the help of the following class:

public static class ODataBuilder
{
    public static ODataQueryOptions<T> BuildOptions<T>(HttpRequest request)
    {
        var modelBuilder = new ODataConventionModelBuilder();
        modelBuilder.AddEntityType(typeof(T));
        var edmModel = modelBuilder.GetEdmModel();
        var oDataQueryContext = new ODataQueryContext(edmModel, typeof(T), new ODataPath());

        return new ODataQueryOptions<T>(oDataQueryContext, request);
    }
}

The advantage of this approach is that the user can now specify required parameters and run requests in Swagger.

If Swagger throws InvalidOperationException with the message "Add at least one media type to the list of supported media types", it means that some OData formatters have unsupported media types.

Since service uses only native parameters and doesn't respond with OData, you can safely remove all OData formatters at startup code and resolve this issue:

services.AddMvcCore(options =>
{
    var inputFormattersToRemove = options.InputFormatters.OfType<ODataInputFormatter>().ToList();
    foreach (var formatter in inputFormattersToRemove)
    {
        options.InputFormatters.Remove(formatter);
    }

    var outputFormattersToRemove = options.OutputFormatters.OfType<ODataOutputFormatter>().ToList();
    foreach (var formatter in outputFormattersToRemove)
    {
        options.OutputFormatters.Remove(formatter);
    }
});
like image 91
Andrey Rodin Avatar answered Oct 19 '25 22:10

Andrey Rodin



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!