OData WebAPI實踐-與ABP vNext整合

2023-05-16 15:00:37

本文屬於 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());
            });
        });
    }

執行後,大功告成:

總結

本文實現了 OData 在 ABP vNext 中的使用。請注意,本方案只是一個 Demo,應用到生產前請自行評估風險,期待 ABP 團隊在未來正式支援 OData 吧。本文的完整程式碼在 github,執行前可能需要先執行資料庫初始化。