如何在不通过Controller的情况下让OData在.NET Core 6中生效?
无Controller场景下OData 8 + .NET 6的实现方案
核心思路
跳过Controller直接在服务端业务类中处理OData查询,核心是手动解析OData查询参数,通过ODataQueryOptions.ApplyTo()方法将查询规则应用到EF Core的IQueryable,最终封装成兼容客户端的ODataServiceResult返回。
步骤1:基础配置(Program.cs)
即使不用Controller,仍需配置OData核心服务和EDM模型:
// 注册HttpContext访问器(用于获取请求查询参数) builder.Services.AddHttpContextAccessor(); // 配置OData服务 builder.Services.AddControllers().AddOData(options => { // 启用所需的OData查询功能 options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(100); // 注册EDM模型 options.AddRouteComponents("odata", GetEdmModel()); }); // 定义EDM模型(映射PersonDto结构) private static IEdmModel GetEdmModel() { ODataConventionModelBuilder modelBuilder = new(); modelBuilder.EntitySet<PersonDto>("Persons"); return modelBuilder.GetEdmModel(); }
步骤2:改造服务端PersonService
注入必要依赖,实现OData查询解析与应用:
public class ServerPersonService { private readonly AppDbContext _context; private readonly IHttpContextAccessor _httpContextAccessor; private readonly IEdmModel _edmModel; private readonly ODataQuerySettings _querySettings; public ServerPersonService(AppDbContext context, IHttpContextAccessor httpContextAccessor, IEdmModel edmModel) { _context = context; _httpContextAccessor = httpContextAccessor; _edmModel = edmModel; // 配置OData查询全局设置 _querySettings = new ODataQuerySettings { PageSize = 100 }; } // 兼容客户端参数+Http请求两种调用方式 public async Task<ODataServiceResult<PersonDto>> GetPeopleAsync( string filter = null, int? top = null, int? skip = null, string orderby = null, string expand = null, string select = null, bool? count = null) { HttpRequest request = _httpContextAccessor.HttpContext?.Request; ODataQueryOptions<PersonDto> queryOptions; if (request != null && !string.IsNullOrEmpty(request.QueryString.Value)) { // 从Http请求中解析OData查询参数 queryOptions = new ODataQueryOptions<PersonDto>(_edmModel, request); } else { // 从传入参数构建模拟查询字符串 var queryParams = new List<string>(); if (!string.IsNullOrEmpty(filter)) queryParams.Add($"$filter={Uri.EscapeDataString(filter)}"); if (top.HasValue) queryParams.Add($"$top={top.Value}"); if (skip.HasValue) queryParams.Add($"$skip={skip.Value}"); if (!string.IsNullOrEmpty(orderby)) queryParams.Add($"$orderby={Uri.EscapeDataString(orderby)}"); if (!string.IsNullOrEmpty(expand)) queryParams.Add($"$expand={Uri.EscapeDataString(expand)}"); if (!string.IsNullOrEmpty(select)) queryParams.Add($"$select={Uri.EscapeDataString(select)}"); if (count.HasValue) queryParams.Add($"$count={count.Value.ToString().ToLower()}"); var mockRequest = new HttpRequest(new DefaultHttpContext()) { QueryString = new QueryString($"?{string.Join("&", queryParams)}") }; queryOptions = new ODataQueryOptions<PersonDto>(_edmModel, mockRequest); } // 从数据库获取原始查询 var baseQuery = _context.Persons.ConvertToDto().AsQueryable(); // 将OData查询规则应用到IQueryable(EF Core会自动转成SQL) var queriedData = queryOptions.ApplyTo(baseQuery, _querySettings) as IQueryable<PersonDto>; // 处理计数请求(需考虑过滤条件) long? totalCount = null; if (queryOptions.Count != null && queryOptions.Count.Value) { var countQuery = queryOptions.Filter != null ? queryOptions.Filter.ApplyTo(baseQuery, _querySettings) as IQueryable<PersonDto> : baseQuery; totalCount = await countQuery.LongCountAsync(); } // 执行查询获取数据 var resultData = await queriedData.ToListAsync(); // 封装成客户端兼容的结果格式 return new ODataServiceResult<PersonDto> { Value = resultData.AsQueryable(), Count = totalCount }; } }
步骤3:简化控制器调用(可选)
如果仍需要对外暴露API,控制器只需调用服务即可:
[ApiController] [Route("odata/[controller]")] public class PersonsController : ControllerBase { private readonly ServerPersonService _personService; public PersonsController(ServerPersonService personService) { _personService = personService; } [HttpGet] public async Task<ActionResult<ODataServiceResult<PersonDto>>> Get() { var result = await _personService.GetPeopleAsync(); return Ok(result); } }
关键注意事项
- EDM模型必须准确:OData依赖EDM模型解析查询规则,需确保
PersonDto的属性映射正确。 - 计数逻辑优化:不能直接使用原始表的
Count(),需应用过滤条件后再计数,避免数据不准确。 - 参数兼容:服务同时支持从Http请求和直接传入参数解析OData查询,适配Blazor预渲染和客户端API调用两种场景。
内容的提问来源于stack exchange,提问作者demis15
相关产品推荐
相关产品推荐

