I'm using OData with ASP.NET Core and Mediator to handle filtering, sorting, etc. of my Person entities. In my controller, I have the following code:
[HttpGet]
public async Task<IActionResult> Get(ODataQueryOptions<Person> queryOptions, CancellationToken cancellationToken)
{
var query = new GetFilteredPersonQuery(queryOptions);
var result = await _mediator.Send(query, cancellationToken);
return Ok(result);
}
The ODataQueryOptions
are passed from the client via query string parameters. In swagger it looks like this I would like to make it so that in Swagger, users can enter only the relevant OData query parameters (e.g., $filter, $orderby, etc.) without having to fill out the entire ODataQueryOptions
object.
Is there a way to achieve this behavior while passing ODataQueryOptions
to the Mediator?
You could add [SwaggerIgnore]
attribute before ODataQueryOptions
and add required query parameter like [FromQuery(Name ="$orderby")] string? orderby
in your controller action:
A minimal example:
Project file:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OData" Version="8.2.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
</Project>
Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Customer>("Customers");
builder.Services.AddControllers().AddOData(
options => options.Select().Filter().OrderBy().Expand().SetMaxTop(null).AddRouteComponents(
routePrefix: "odata",
model: modelBuilder.GetEdmModel()));
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Models:
public class Customer
{
[Key]
public int Id { get; set; }
public String Name { get; set; }
public int Age { get; set; }
public List<Order> Orders { get; set; }
}
public class Order
{
[Key]
public int Id { get; set; }
public int Price { get; set; }
public int Quantity { get; set; }
}
Controller:
public class CustomersController : ODataController
{
private static List<Order> Orders = new List<Order>
{
new Order { Id = 1001, Price = 10, Quantity = 10 },
new Order { Id = 1002, Price = 35, Quantity = 2 },
new Order { Id = 1003, Price = 70, Quantity = 5 },
new Order { Id = 1004, Price = 20, Quantity = 20 },
new Order { Id = 1005, Price = 40, Quantity = 15 },
new Order { Id = 1006, Price = 15, Quantity = 50 },
};
private static List<Customer> Customers = new List<Customer>
{
new Customer { Id = 1, Name = "Customer 1", Age = 33, Orders = new List<Order>(){Orders[0], Orders[1]} },
new Customer { Id = 2, Name = "Customer 2", Age = 32, Orders = new List<Order>(){Orders[2], Orders[3]} },
new Customer { Id = 3, Name = "Customer 3", Age = 31, Orders = new List<Order>(){Orders[4], Orders[5]} }
};
public IQueryable<Customer> Get([SwaggerIgnore] ODataQueryOptions<Customer> options,
[FromQuery(Name ="$orderby")] string? orderby,
[FromQuery(Name = "$skip")] string? skip,
[FromQuery(Name = "$filter")] string? filter)
{
IQueryable results =options.ApplyTo(Customers.AsQueryable(), new ODataQuerySettings());
return results as IQueryable<Customer>;
}
}
Result:
If there's many controllers in your project ,you may create an OperationFilter instead of adding parameters one by one:
public class ODataQueryFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null) operation.Parameters = new List<OpenApiParameter>();
var descriptor = context.ApiDescription.ActionDescriptor as ControllerActionDescriptor;
// replace the logic here
if (descriptor != null && descriptor.ControllerName.Contains("Customer"))
{
operation.Parameters.Add(new OpenApiParameter()
{
Name = "$orderby",
In = ParameterLocation.Query,
Description = "OdataQuery",
Required = false
});
operation.Parameters.Add(new OpenApiParameter()
{
Name = "$skip",
In = ParameterLocation.Query,
Description = "OdataQuery",
Required = false
});
operation.Parameters.Add(new OpenApiParameter()
{
Name = "$skip",
In = ParameterLocation.Query,
Description = "OdataQuery",
Required = false
});
}
}
}
Register it :
builder.Services.AddSwaggerGen(c=>c.OperationFilter<ODataQueryFilter>() );