Getting OData Count in ASP.NET Core WebAPI
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; }
}
}
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
.