1. 程式人生 > 其它 >ABP VNext框架中HttpApi模組 在ABP VNext框架中對HttpApi模組的控制器進行基類封裝

ABP VNext框架中HttpApi模組 在ABP VNext框架中對HttpApi模組的控制器進行基類封裝

在ABP VNext框架中對HttpApi模組的控制器進行基類封裝

 

在ABP VNext框架中,HttpApi專案是我們作為Restful格式的控制器物件的封裝專案,但往往很多案例都是簡單的繼承基類控制器AbpControllerBase,而需要在每個控制器裡面重寫很多類似的Create/Update/Delete/Get/GetList等常規Restful介面的呼叫,千篇一律的重複,本篇隨筆介紹如何對這些內容通過基類的方式實現,子類無需重複程式碼,並且強型別所有的介面實現。

1、Restful介面的CRUD實現

在我們使用HttpApi專案進一步封裝ABP VNext框架的Application專案中的應用服務,作為Restful格式的控制器物件,往往都需要實現基本的Create/Update/Delete/Get/GetList等常規Restful介面的實現呼叫,官方很多案例也都是把這部分程式碼進行重複在重複,如下所示。

例如對於客戶物件Customer的HttpApi專案控制器的程式碼如下:

    /// <summary>
    /// 客戶資訊控制器
    /// </summary>
    //[Area("crm")]
    [RemoteService]
    [ControllerName("Customer")]
    [Route("api/customer")]
    public class CustomerController : AbpControllerBase, ICustomerAppService
    {
        private readonly ICustomerAppService _customerAppService;

        public CustomerController(ICustomerAppService customerAppService)
        {
            _customerAppService = customerAppService;
        }

        /// <summary>
        /// 建立物件
        /// </summary>
        [HttpPost]
        public Task<CustomerDto> CreateAsync(CreateCustomerDto input)
        {
            return _customerAppService.CreateAsync(input);
        }

        /// <summary>
        /// 刪除物件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete]
        [Route("{id}")]
        public Task DeleteAsync(string id)
        {
            return _customerAppService.DeleteAsync(id);
        }

        /// <summary>
        /// 根據ID獲取指定物件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("{id}")]
        public Task<CustomerDto> GetAsync(string id)
        {
            return _customerAppService.GetAsync(id);
        }

        /// <summary>
        /// 分頁獲取列表記錄
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpGet]
        public Task<PagedResultDto<CustomerDto>> GetListAsync(CustomerPagedDto input)
        {
            return _customerAppService.GetListAsync(input);
        }

        /// <summary>
        /// 更新物件
        /// </summary>
        [HttpPut]
        [Route("{id}")]
        public Task<CustomerDto> UpdateAsync(string id, CustomerDto input)
        {
            return _customerAppService.UpdateAsync(id, input);
        }

        /// <summary>
        /// 獲取欄位列別名
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("columnalias")]
        public Task<Dictionary<string, string>> GetColumnNameAlias()
        {
            return _customerAppService.GetColumnNameAlias();
        }
    }

對於其他業務物件,這部分基本上千篇一律的重複一次,就是為了簡單的封裝一下CRUD的常規介面。

那麼我們是否可以考慮通過基類的方式來抽取這部分程式碼,放到基類裡面去實現,以後只需要繼承該基類就完事了呢?

考慮到這些Restful的API介面實現,很多都是特定的業務物件,如上面的CustomerDto、CustomerPagedDto 等,那這些就需要通過泛型的方式指定型別給基類了。

而業務介面 ICustomerAppService 也是一個特定的業務介面,也需要傳遞給基類處理,這樣才能進行呼叫的。

2、HttpApi 基類控制器的實現

我們注意到上面專案的ICustomerService的介面定義如下:

    /// <summary>
    /// 客戶資訊,應用層服務介面定義
    /// </summary>
    public interface ICustomerAppService : 
        ICrudAppService<CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>
    {
        ///// <summary>
        ///// 獲取指定條件的數量
        ///// </summary>
        ///// <param name="input">查詢條件</param>
        ///// <returns></returns>
        //Task<int> CountAsync(CustomerPagedDto input);

        /// <summary>
        /// 獲取欄位中文別名(用於介面顯示)的字典集合
        /// </summary>
        /// <returns></returns>
        Task<Dictionary<string, string>> GetColumnNameAlias();

    }

它是繼承自ICrudAppService介面(Abp的基類介面)並傳遞幾個相關的實體類引數作為基類的介面強型別構建的。

那麼我們的HttpApi 基類控制器也可以採用這種方式來傳遞對應的型別,作為基類介面的處理需要。

我們定義一個控制器基類MyAbpControllerBase,讓它繼承自常規的AbpControllerBase介面,並實現ICrudAppService介面,如下所示。

    /// <summary>
    /// 自定義ABP控制器基類,用於實現通用的CRUD等方法
    /// </summary>
    public abstract class MyAbpControllerBase<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> : AbpControllerBase, 
        IMyCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
        where TEntityDto : IEntityDto<TKey>
        where TGetListInput : IPagedAndSortedResultRequest
        where TCreateInput : IEntityDto<TKey>
        where TUpdateInput : IEntityDto<TKey>
    {
        protected IMyCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> _service;

        public MyAbpControllerBase(IMyCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> service)
        {
            _service = service;
        }

這樣我們就定義好這個基類,並且通過讓它傳遞相關的業務物件和物件外來鍵型別,強型別相關的介面處理,並讓它實現了相關的建構函式。

那麼對應的介面實現,我們只需要呼叫 _service 的處理即可。

        /// <summary>
        /// 建立物件
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost]
        public Task<TEntityDto> CreateAsync(TCreateInput input)
        {
            return _service.CreateAsync(input);
        }

        /// <summary>
        /// 刪除物件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete]
        [Route("{id}")]
        public Task DeleteAsync(TKey id)
        {
            return _service.DeleteAsync(id);
        }

        /// <summary>
        /// 獲取指定id的記錄
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("{id}")]
        public Task<TEntityDto> GetAsync(TKey id)
        {
            return _service.GetAsync(id);
        }

        /// <summary>
        /// 獲取條件的列表
        /// </summary>
        [HttpGet]
        public Task<PagedResultDto<TEntityDto>> GetListAsync(TGetListInput input)
        {
            return _service.GetListAsync(input);
        }

        /// <summary>
        /// 更新物件
        /// </summary>
        [HttpPut]
        [Route("{id}")]
        public Task<TEntityDto> UpdateAsync(TKey id, TUpdateInput input)
        {
            return _service.UpdateAsync(id, input);
        }

我們還可以自己增加一些特殊的介面和基類的實現,這樣我們對於常規的介面就不需要新增重複的實現程式碼了,只需要繼承基類就可以了。

子類繼承基類的程式碼如下所示。

    /// <summary>
    /// 客戶資訊控制器
    /// </summary>
    [RemoteService]
    [ControllerName("Customer")]
    [Route("api/customer")]
    public class CustomerController : 
        MyAbpControllerBase<CustomerDto, string, CustomerPagedDto,CreateCustomerDto, CustomerDto>, 
        ICustomerAppService
    {
        private readonly ICustomerAppService _customerAppService;

        public CustomerController(ICustomerAppService customerAppService) : base(customerAppService)
        {
            _customerAppService = customerAppService;
        }
    }

這樣這個CustomerController預設就具有所有相關的常規介面了,不需要千篇一律的重寫那些繁雜的程式碼,清爽了很多。

而如果我們需要額外增加一些介面的處理,那麼在其介面定義增加,並實現即可,如下程式碼所示。

    /// <summary>
    /// 客戶資訊,應用層服務介面定義
    /// </summary>
    public interface ICustomerAppService : 
        IMyCrudAppService<CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>
    {

        /// <summary>
        /// 增加的額外測試介面
        /// </summary>
        /// <returns></returns>
        Task<bool> TestExtra();
    }

HttpApi專案的實現程式碼如下所示。

    /// <summary>
    /// 客戶資訊控制器
    /// </summary>
    [RemoteService]
    [ControllerName("Customer")]
    [Route("api/customer")]
    public class CustomerController : 
        MyAbpControllerBase<CustomerDto, string, CustomerPagedDto,CreateCustomerDto, CustomerDto>, 
        ICustomerAppService
    {
        private readonly ICustomerAppService _customerAppService;

        public CustomerController(ICustomerAppService customerAppService) : base(customerAppService)
        {
            _customerAppService = customerAppService;
        }

        /// <summary>
        /// 測試額外的介面呼叫
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("test-extra")]
        public async Task<bool> TestExtra()
        {
            return await _customerAppService.TestExtra();
        }
    }

啟動Swagger的檢視介面介面,我們可以看到,Customer控制器所釋出的介面資訊,如下所示。

 

 一切都是那麼的美好,以後再也不用重複書寫或看到那些重複的,沒有技術含量的程式碼了。

在ABP VNext框架中,HttpApi專案是我們作為Restful格式的控制器物件的封裝專案,但往往很多案例都是簡單的繼承基類控制器AbpControllerBase,而需要在每個控制器裡面重寫很多類似的Create/Update/Delete/Get/GetList等常規Restful介面的呼叫,千篇一律的重複,本篇隨筆介紹如何對這些內容通過基類的方式實現,子類無需重複程式碼,並且強型別所有的介面實現。

1、Restful介面的CRUD實現

在我們使用HttpApi專案進一步封裝ABP VNext框架的Application專案中的應用服務,作為Restful格式的控制器物件,往往都需要實現基本的Create/Update/Delete/Get/GetList等常規Restful介面的實現呼叫,官方很多案例也都是把這部分程式碼進行重複在重複,如下所示。

例如對於客戶物件Customer的HttpApi專案控制器的程式碼如下:

    /// <summary>
    /// 客戶資訊控制器
    /// </summary>
    //[Area("crm")]
    [RemoteService]
    [ControllerName("Customer")]
    [Route("api/customer")]
    public class CustomerController : AbpControllerBase, ICustomerAppService
    {
        private readonly ICustomerAppService _customerAppService;

        public CustomerController(ICustomerAppService customerAppService)
        {
            _customerAppService = customerAppService;
        }

        /// <summary>
        /// 建立物件
        /// </summary>
        [HttpPost]
        public Task<CustomerDto> CreateAsync(CreateCustomerDto input)
        {
            return _customerAppService.CreateAsync(input);
        }

        /// <summary>
        /// 刪除物件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete]
        [Route("{id}")]
        public Task DeleteAsync(string id)
        {
            return _customerAppService.DeleteAsync(id);
        }

        /// <summary>
        /// 根據ID獲取指定物件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("{id}")]
        public Task<CustomerDto> GetAsync(string id)
        {
            return _customerAppService.GetAsync(id);
        }

        /// <summary>
        /// 分頁獲取列表記錄
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpGet]
        public Task<PagedResultDto<CustomerDto>> GetListAsync(CustomerPagedDto input)
        {
            return _customerAppService.GetListAsync(input);
        }

        /// <summary>
        /// 更新物件
        /// </summary>
        [HttpPut]
        [Route("{id}")]
        public Task<CustomerDto> UpdateAsync(string id, CustomerDto input)
        {
            return _customerAppService.UpdateAsync(id, input);
        }

        /// <summary>
        /// 獲取欄位列別名
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("columnalias")]
        public Task<Dictionary<string, string>> GetColumnNameAlias()
        {
            return _customerAppService.GetColumnNameAlias();
        }
    }

對於其他業務物件,這部分基本上千篇一律的重複一次,就是為了簡單的封裝一下CRUD的常規介面。

那麼我們是否可以考慮通過基類的方式來抽取這部分程式碼,放到基類裡面去實現,以後只需要繼承該基類就完事了呢?

考慮到這些Restful的API介面實現,很多都是特定的業務物件,如上面的CustomerDto、CustomerPagedDto 等,那這些就需要通過泛型的方式指定型別給基類了。

而業務介面 ICustomerAppService 也是一個特定的業務介面,也需要傳遞給基類處理,這樣才能進行呼叫的。

2、HttpApi 基類控制器的實現

我們注意到上面專案的ICustomerService的介面定義如下:

    /// <summary>
    /// 客戶資訊,應用層服務介面定義
    /// </summary>
    public interface ICustomerAppService : 
        ICrudAppService<CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>
    {
        ///// <summary>
        ///// 獲取指定條件的數量
        ///// </summary>
        ///// <param name="input">查詢條件</param>
        ///// <returns></returns>
        //Task<int> CountAsync(CustomerPagedDto input);

        /// <summary>
        /// 獲取欄位中文別名(用於介面顯示)的字典集合
        /// </summary>
        /// <returns></returns>
        Task<Dictionary<string, string>> GetColumnNameAlias();

    }

它是繼承自ICrudAppService介面(Abp的基類介面)並傳遞幾個相關的實體類引數作為基類的介面強型別構建的。

那麼我們的HttpApi 基類控制器也可以採用這種方式來傳遞對應的型別,作為基類介面的處理需要。

我們定義一個控制器基類MyAbpControllerBase,讓它繼承自常規的AbpControllerBase介面,並實現ICrudAppService介面,如下所示。

    /// <summary>
    /// 自定義ABP控制器基類,用於實現通用的CRUD等方法
    /// </summary>
    public abstract class MyAbpControllerBase<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> : AbpControllerBase, 
        IMyCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput>
        where TEntityDto : IEntityDto<TKey>
        where TGetListInput : IPagedAndSortedResultRequest
        where TCreateInput : IEntityDto<TKey>
        where TUpdateInput : IEntityDto<TKey>
    {
        protected IMyCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> _service;

        public MyAbpControllerBase(IMyCrudAppService<TEntityDto, TKey, TGetListInput, TCreateInput, TUpdateInput> service)
        {
            _service = service;
        }

這樣我們就定義好這個基類,並且通過讓它傳遞相關的業務物件和物件外來鍵型別,強型別相關的介面處理,並讓它實現了相關的建構函式。

那麼對應的介面實現,我們只需要呼叫 _service 的處理即可。

        /// <summary>
        /// 建立物件
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost]
        public Task<TEntityDto> CreateAsync(TCreateInput input)
        {
            return _service.CreateAsync(input);
        }

        /// <summary>
        /// 刪除物件
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpDelete]
        [Route("{id}")]
        public Task DeleteAsync(TKey id)
        {
            return _service.DeleteAsync(id);
        }

        /// <summary>
        /// 獲取指定id的記錄
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        [HttpGet]
        [Route("{id}")]
        public Task<TEntityDto> GetAsync(TKey id)
        {
            return _service.GetAsync(id);
        }

        /// <summary>
        /// 獲取條件的列表
        /// </summary>
        [HttpGet]
        public Task<PagedResultDto<TEntityDto>> GetListAsync(TGetListInput input)
        {
            return _service.GetListAsync(input);
        }

        /// <summary>
        /// 更新物件
        /// </summary>
        [HttpPut]
        [Route("{id}")]
        public Task<TEntityDto> UpdateAsync(TKey id, TUpdateInput input)
        {
            return _service.UpdateAsync(id, input);
        }

我們還可以自己增加一些特殊的介面和基類的實現,這樣我們對於常規的介面就不需要新增重複的實現程式碼了,只需要繼承基類就可以了。

子類繼承基類的程式碼如下所示。

    /// <summary>
    /// 客戶資訊控制器
    /// </summary>
    [RemoteService]
    [ControllerName("Customer")]
    [Route("api/customer")]
    public class CustomerController : 
        MyAbpControllerBase<CustomerDto, string, CustomerPagedDto,CreateCustomerDto, CustomerDto>, 
        ICustomerAppService
    {
        private readonly ICustomerAppService _customerAppService;

        public CustomerController(ICustomerAppService customerAppService) : base(customerAppService)
        {
            _customerAppService = customerAppService;
        }
    }

這樣這個CustomerController預設就具有所有相關的常規介面了,不需要千篇一律的重寫那些繁雜的程式碼,清爽了很多。

而如果我們需要額外增加一些介面的處理,那麼在其介面定義增加,並實現即可,如下程式碼所示。

    /// <summary>
    /// 客戶資訊,應用層服務介面定義
    /// </summary>
    public interface ICustomerAppService : 
        IMyCrudAppService<CustomerDto, string, CustomerPagedDto, CreateCustomerDto, CustomerDto>
    {

        /// <summary>
        /// 增加的額外測試介面
        /// </summary>
        /// <returns></returns>
        Task<bool> TestExtra();
    }

HttpApi專案的實現程式碼如下所示。

    /// <summary>
    /// 客戶資訊控制器
    /// </summary>
    [RemoteService]
    [ControllerName("Customer")]
    [Route("api/customer")]
    public class CustomerController : 
        MyAbpControllerBase<CustomerDto, string, CustomerPagedDto,CreateCustomerDto, CustomerDto>, 
        ICustomerAppService
    {
        private readonly ICustomerAppService _customerAppService;

        public CustomerController(ICustomerAppService customerAppService) : base(customerAppService)
        {
            _customerAppService = customerAppService;
        }

        /// <summary>
        /// 測試額外的介面呼叫
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [Route("test-extra")]
        public async Task<bool> TestExtra()
        {
            return await _customerAppService.TestExtra();
        }
    }

啟動Swagger的檢視介面介面,我們可以看到,Customer控制器所釋出的介面資訊,如下所示。

 

 一切都是那麼的美好,以後再也不用重複書寫或看到那些重複的,沒有技術含量的程式碼了。