本文属于 OData 系列文章
ABP 是一个流行的 ASP. NET 开发框架,旧版的的 ABP 已经能够非常好的支持了 OData ,并提供了对应的 OData 包。
ABP vNext 是一个重新设计的,面向微服务的框架,提供了一些非常有用的特性,包括分页查询等但是它并不能原生支持 OData ,我们需要自行实现。
本文的实现方式本质上为
side by side
方式,由于 ABP vNext 官方没有对应的设计,所以你依然需要自己编写控制器。
本文使用 ABP CLI 6.0.3 生成的 ABP vNext 项目、Microsoft.AspNetCore.OData 8.1.2
原理
ABP vNext 有自动生成 Controller 的机制,我们的 ApplicationService,它会帮我们自动生成对应的终结点,并对外提供服务。这个过程是由 ABP 控制的,我们能修改的内容非常有限。
// TodoAppHttpApiHostModule.cs
private void ConfigureConventionalControllers()
{
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ConventionalControllers.Create(typeof(TodoAppApplicationModule).Assembly, options =>
{
//// 移除自动生成的控制器
//options.ControllerTypes.Remove(typeof(TodoAppController));
//options.RootPath = "abp";
//// 添加手动实现的控制器
//options.ControllerTypes.Add(typeof(TodoAppImplController));
});
});
}
它依照惯例生成,我们可以对类型进行增加或者减少,但是不能控制生成的行为。而 OData 的控制器,需要我们从 ODataController
继承,或者使用 [EnableQuery]
,因此我们需要阻止自动生成控制器,自己实现对应的逻辑。
有了对应的控制器,就需要在应用程序模块中配置 OData 的路由、EDM 等信息,并且不要和原有的控制器路由冲突。
实现
首先禁用自动生成控制器功能。
//TodoAppService.cs
[RemoteService(false)]
public class TodoAppService : ApplicationService, ITodoAppService
{
private readonly IRepository<TodoItem, Guid> _todoItemRepository;
public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository)
{
_todoItemRepository = todoItemRepository;
}
public async Task<IQueryable<TodoItemDto>> GetListAsync()
{
var items = await _todoItemRepository.GetQueryableAsync();
return items
.Select(item => new TodoItemDto
{
Id = item.Id,
Text = item.Text
})
}
}
将应用服务打上 [RemoteService(false)]
标记,服务就不会自动生成控制器。接下来在 HttpApi
项目中新建一个与服务同名的控制器,由于 ABP 项目自动生成了控制器抽象类模板,我们继承并实现它即可。
/* Inherit your controllers from this class.
*/
public abstract class TodoAppController : AbpControllerBase
{
protected TodoAppController()
{
LocalizationResource = typeof(TodoAppResource);
}
}
public class TodoController : TodoAppController
{
private readonly ITodoAppService todoAppService;
public TodoController(ITodoAppService todoAppService)
:base()
{
this.todoAppService = todoAppService;
}
[EnableQuery]
public async Task<IEnumerable<TodoItemDto>> GetAsync()
{
var result = await todoAppService.GetListAsync();
return result.AsQueryable();
}
}
注入对应的服务,实现自己的逻辑,如果需要利用 EF Core 的查询功能,请使用 IQueryable
传递数据到控制器层。然后配置 OData :
//TodoAppHttpApiHostModule.cs
private IEdmModel GetEdmModels()
{
var builder = new ODataConventionModelBuilder();
var device = builder.EntitySet<TodoItemDto>("Todo").EntityType.HasKey(w => w.Id);
return builder.GetEdmModel();
}
记得在 PreConfigureServices
中加上调用。
//TodoAppHttpApiHostModule.cs
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<OpenIddictBuilder>(builder =>
{
builder.AddValidation(options =>
{
options.AddAudiences("TodoApp");
options.UseLocalServer();
options.UseAspNetCore();
});
});
// 加上这个调用
PreConfigure<IMvcBuilder>(builder =>
{
builder.AddOData(opt =>
{
opt.RouteOptions.EnablePropertyNameCaseInsensitive = true;
opt.RouteOptions.EnableQualifiedOperationCall = false;
opt.Expand().Filter().Count().OrderBy().Filter().SetMaxTop(30);
opt.AddRouteComponents("api/app/", GetEdmModels());
});
});
}
运行后,大功告成:
![[Pasted image 20230512173232.png]]
总结
本文实现了 OData 在 ABP vNext 中的使用。请注意,本方案只是一个 Demo,应用到生产前请自行评估风险,期待 ABP 团队在未来正式支持 OData
吧。本文的完整代码在 github,运行前可能需要先执行数据库初始化。