Using the sample code from Hassan Habib's Supercharging ASP.NET Core API with OData blog post, I am able to get the record count using an OData query of $count=true
:
What needs to be configured to get the response object to be wrapped in an OData context
so that the @odata.count
property will show?
In my own ASP.NET Core web API project, I cannot get the simple $count
parameter to work and I have no idea why.
With Hassan's sample code, the response JSON is wrapped in an OData context
and the payload (an IEnumerable<Student>
object) is in the value
property of the JSON response. In my project, the OData context
wrapper does not exist; my code never returns OData context
, it only returns the payload object of type IEnumerable<T>
:
I've also noticed that the Content-Type
in the response header is application/json; odata.metadata=minimal; odata.streaming=true; charset=utf-8
in the sample project, where as it is simply application/json; charset=utf-8
in my project. I don't see any setting that controls this in either project, so I'm assuming the Microsoft.AspNetCore.Odata
NuGet package is magically changing the response when it's configured properly.
My project is also using .NET Core 2.2 (Upgraded from 2.1), all the same versions of NuGet packages as Hassan's sample projects, and all the same settings in the StartUp.cs
class... although my StartUp.cs
is way more complicated (hence the reason I'm not posting it's content here.)
Just been battling this.
I found that if I request my controller at /api/Things
that most of the OData options work but $count
doesn't.
However, $count
does work if I request the same method via /odata/Things
.
You can just set an empty preffix route when you map OData, and you will receive OData with your request your endpoint.
routeBuilder.MapODataServiceRoute("ODataEdmModel", "", GetEdmModel());
I could reproduce your issue when i use [Route("api/[controller]")]
and [ApiController]
with the startup.cs like below:
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter();
routeBuilder.EnableDependencyInjection();
});
To fix it,be sure you have built a private method to do a handshake between your existing data models (OData model in this case) and EDM.
Here is a simple demo:
1.Controller(comment on Route
attribute and ApiController
attribute):
//[Route("api/[controller]")]
//[ApiController]
public class StudentsController : ControllerBase
{
private readonly WSDbContext _context;
public StudentsController(WSDbContext context)
{
_context = context;
}
// GET: api/Students
[HttpGet]
[EnableQuery()]
public IEnumerable<Student> Get()
{
return _context.Students;
}
}
//[Route("api/[controller]")]
//[ApiController]
public class SchoolsController : ControllerBase
{
private readonly WSDbContext _context;
public SchoolsController(WSDbContext context)
{
_context = context;
}
// GET: api/Schools
[HttpGet]
[EnableQuery()]
public IEnumerable<School> Get()
{
return _context.Schools;
}
2.Startup.cs():
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore(action => action.EnableEndpointRouting = false);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var connection = @"Server=(localdb)\mssqllocaldb;Database=WSDB;Trusted_Connection=True;ConnectRetryCount=0";
services.AddDbContext<WSDbContext>(options => options.UseSqlServer(connection));
services.AddOData();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseMvc(routeBuilder =>
{
routeBuilder.Expand().Select().Count().OrderBy().Filter();
routeBuilder.MapODataServiceRoute("api", "api", GetEdmModel());
});
}
private static IEdmModel GetEdmModel()
{
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Student>("Students");
builder.EntitySet<Student>("Schools");
return builder.GetEdmModel();
}
}
In my case I wanted to extend existing Api methods with [EnableQuery]
but have it include the count metadata.
I ended up extending the EnableQuery attribute to return a different reponse, it worked perfectly.
public class EnableQueryWithMetadataAttribute : EnableQueryAttribute
{
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
if (actionExecutedContext.Result is ObjectResult obj && obj.Value is IQueryable qry)
{
obj.Value = new ODataResponse
{
Count = actionExecutedContext.HttpContext.Request.ODataFeature().TotalCount,
Value = qry
};
}
}
public class ODataResponse
{
[JsonPropertyName("@odata.count")]
public long? Count { get; set; }
[JsonPropertyName("value")]
public IQueryable Value { get; set; }
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With