在Asp.net Web API中,對業務數據的分頁查詢處理是一個非常常見的接口,我們需要在查詢條件對象中,定義好相應業務的查詢參數,排序信息,請求記錄數和每頁大小信息等內容,根據這些查詢信息,我們在后端的Asp.net Web API中實現對這些數據的按需獲取,並排序返回給客戶端使用。本篇隨筆介紹利用查詢條件對象,在Asp.net Web API中實現對業務數據的分頁查詢處理。
1、Web API控制器基類關系
為了更好的進行相關方法的封裝處理,我們把一些常規的接口處理放在BaseApiController里面,而把基於業務表的操作接口放在BusinessController里面定義,如下所示。
在BaseApiController里面,我們使用了結果封裝和異常處理的過濾器統一處理,以便簡化代碼,如下控制器類定義。
/// <summary> /// 所有接口基類 /// </summary> [ExceptionHandling] [WrapResult] public class BaseApiController : ApiController
其中ExceptionHandling 和WrapResult的過濾器處理,可以參考我的隨筆《利用過濾器Filter和特性Attribute實現對Web API返回結果的封裝和統一異常處理》進行詳細了解。
而業務類的接口通用封裝,則放在了BusinessController控制器里面,其中使用了泛型定義,包括實體類,業務操作類,分頁條件類等內容作為約束參數,如下所示。
/// <summary> /// 本控制器基類專門為訪問數據業務對象而設的基類 /// </summary> /// <typeparam name="B">業務對象類型</typeparam> /// <typeparam name="T">實體類類型</typeparam> [ApiAuthorize] public class BusinessController<B, T, TGetAllInput> : BaseApiController where B : class where TGetAllInput : IPagedAndSortedResultRequest where T : BaseEntity, new()
2、分頁處理接口
其中IPagedAndSortedResultRequest接口,是借鑒ABP框架中對於分頁部分的處理,因此分頁函數需要實現這個接口,這個接口包含了請求的數量,偏移量, 以及排序等屬性定義的。
而BusinessController的分頁查詢處理函數GetAll定義如下所示。
/// <summary> /// 分頁獲取記錄 /// </summary> /// <param name="input"></param> /// <returns></returns> [HttpGet] public virtual PagedResultDto<T> GetAll([FromUri] TGetAllInput input) { var condition = GetCondition(input); var list = GetPagedData(condition, input); return list; }
其中 GetCondition 函數是給子類進行重寫,以便處理不同的條件查詢的。我們以UserController控制器為例進行說明。
/// <summary> /// 用戶信息的業務控制器 /// </summary> public class UserController : BusinessController<User, UserInfo, UserPagedDto>
其中傳入的User是BLL業務層類,用來操作數據庫;UserInfo是實體類,用來傳遞記錄信息;UserPagedDto 則是分頁查詢條件類。
/// <summary> /// 用戶信息的業務查詢類 /// </summary> public class UserPagedDto : PagedAndSortedInputDto, IPagedAndSortedResultRequest { /// <summary> /// 默認構造函數 /// </summary> public UserPagedDto() : base() { } /// <summary> /// 參數化構造函數 /// </summary> /// <param name="skipCount">跳過的數量</param> /// <param name="resultCount">最大結果集數量</param> public UserPagedDto(int skipCount, int resultCount) : base(skipCount, resultCount) { } /// <summary> /// 使用分頁信息進行初始化SkipCount 和 MaxResultCount /// </summary> /// <param name="pagerInfo">分頁信息</param> public UserPagedDto(PagerInfo pagerInfo) : base(pagerInfo) { } #region Property Members /// <summary> /// 所屬角色ID /// </summary> public virtual int? Role_ID { get; set; } public virtual int? ID { get; set; } /// <summary> /// 用戶編碼 /// </summary> public virtual string HandNo { get; set; } /// <summary> /// 用戶名/登錄名 /// </summary> public virtual string Name { get; set; } /// <summary> /// 用戶密碼 /// </summary> public virtual string Password { get; set; } /// <summary> /// 用戶全名 /// </summary> public virtual string FullName { get; set; } /// <summary> /// 移動電話 /// </summary> public virtual string MobilePhone { get; set; } /// <summary> /// 郵件地址 /// </summary> public virtual string Email { get; set; } /// <summary> /// 默認部門ID /// </summary> public virtual string Dept_ID { get; set; } /// <summary> /// 所屬機構ID /// </summary> public virtual string Company_ID { get; set; } /// <summary> /// 父ID /// </summary> public virtual int? PID { get; set; } /// <summary> /// 用戶呢稱 /// </summary> public virtual string Nickname { get; set; } /// <summary> /// 是否過期 /// </summary> public virtual bool? IsExpire { get; set; } /// <summary> /// 過期日期 /// </summary> public virtual DateTime? ExpireDateStart { get; set; } public virtual DateTime? ExpireDateEnd { get; set; } /// <summary> /// 職務頭銜 /// </summary> public virtual string Title { get; set; } /// <summary> /// 身份證號碼 /// </summary> public virtual string IdentityCard { get; set; } /// <summary> /// 辦公電話 /// </summary> public virtual string OfficePhone { get; set; } /// <summary> /// 家庭電話 /// </summary> public virtual string HomePhone { get; set; } /// <summary> /// 住址 /// </summary> public virtual string Address { get; set; } /// <summary> /// 辦公地址 /// </summary> public virtual string WorkAddr { get; set; } /// <summary> /// 性別 /// </summary> public virtual string Gender { get; set; } /// <summary> /// 出生日期 /// </summary> public virtual DateTime? BirthdayStart { get; set; } public virtual DateTime? BirthdayEnd { get; set; } /// <summary> /// QQ號碼 /// </summary> public virtual string QQ { get; set; } /// <summary> /// 個性簽名 /// </summary> public virtual string Signature { get; set; } /// <summary> /// 審核狀態 /// </summary> public virtual string AuditStatus { get; set; } /// <summary> /// 備注 /// </summary> public virtual string Note { get; set; } /// <summary> /// 自定義字段 /// </summary> public virtual string CustomField { get; set; } /// <summary> /// 默認部門名稱 /// </summary> public virtual string DeptName { get; set; } /// <summary> /// 所屬機構名稱 /// </summary> public virtual string CompanyName { get; set; } /// <summary> /// 排序碼 /// </summary> public virtual string SortCode { get; set; } /// <summary> /// 創建人 /// </summary> public virtual string Creator { get; set; } /// <summary> /// 創建人ID /// </summary> public virtual string Creator_ID { get; set; } /// <summary> /// 創建時間 /// </summary> public virtual DateTime? CreateTimeStart { get; set; } public virtual DateTime? CreateTimeEnd { get; set; } /// <summary> /// 編輯人 /// </summary> public virtual string Editor { get; set; } /// <summary> /// 編輯人ID /// </summary> public virtual string Editor_ID { get; set; } /// <summary> /// 編輯時間 /// </summary> public virtual DateTime? EditTimeStart { get; set; } public virtual DateTime? EditTimeEnd { get; set; } /// <summary> /// 是否已刪除 /// </summary> public virtual bool? Deleted { get; set; } /// <summary> /// 當前登錄IP /// </summary> public virtual string CurrentLoginIP { get; set; } /// <summary> /// 當前登錄時間 /// </summary> public virtual DateTime CurrentLoginTime { get; set; } /// <summary> /// 當前Mac地址 /// </summary> public virtual string CurrentMacAddress { get; set; } /// <summary> /// 微信綁定的OpenId /// </summary> public virtual string OpenId { get; set; } /// <summary> /// 微信多平台應用下的統一ID /// </summary> public virtual string UnionId { get; set; } /// <summary> /// 公眾號狀態 /// </summary> public virtual string Status { get; set; } /// <summary> /// 公眾號 /// </summary> public virtual string SubscribeWechat { get; set; } /// <summary> /// 科室權限 /// </summary> public virtual string DeptPermission { get; set; } /// <summary> /// 企業微信UserID /// </summary> public virtual string CorpUserId { get; set; } /// <summary> /// 企業微信狀態 /// </summary> public virtual string CorpStatus { get; set; } #endregion }
它的基類屬性包括了MaxResultCount,SkipCount,Sorting等分頁排序所需的信息。
另外還包含了對條件查詢的屬性信息,如果是數值的,布爾類型的,則是可空類型,日期則有起始條件的范圍屬性等等,也可以根據自己需要定義更多屬性用戶過濾條件。
如對於出生日期,我們定義一個區間范圍來進行查詢。
/// <summary> /// 出生日期 /// </summary> public virtual DateTime? BirthdayStart { get; set; } public virtual DateTime? BirthdayEnd { get; set; }
最后,我們根據需要進行判斷,獲得查詢條件即可。
/// <summary> /// 獲取查詢條件並轉換為SQL /// </summary> /// <param name="input">查詢條件</param> protected override string GetCondition(UserPagedDto input) { //根據條件,構建SQL條件語句 SearchCondition condition = new SearchCondition(); if (!input.Role_ID.HasValue) { condition.AddCondition("ID", input.ID, SqlOperator.Equal) .AddCondition("IdentityCard", input.IdentityCard, SqlOperator.Equal) .AddCondition("Name", input.Name, SqlOperator.Like) .AddCondition("Note", input.Note, SqlOperator.Like) .AddCondition("Email", input.Email, SqlOperator.Like) .AddCondition("MobilePhone", input.MobilePhone, SqlOperator.Like) .AddCondition("Address", input.Address, SqlOperator.Like) .AddCondition("HandNo", input.HandNo, SqlOperator.Like) .AddCondition("HomePhone", input.HomePhone, SqlOperator.Like) .AddCondition("Nickname", input.Nickname, SqlOperator.Like) .AddCondition("OfficePhone", input.OfficePhone, SqlOperator.Like) .AddCondition("OpenId", input.OpenId, SqlOperator.Like) .AddCondition("Password", input.Password, SqlOperator.Like) .AddCondition("PID", input.PID, SqlOperator.Like) .AddCondition("QQ", input.QQ, SqlOperator.Equal) .AddCondition("DeptPermission", input.DeptPermission, SqlOperator.Like) .AddCondition("AuditStatus", input.AuditStatus, SqlOperator.Equal) .AddCondition("FullName", input.FullName, SqlOperator.Like) .AddCondition("Gender", input.Gender, SqlOperator.Equal) .AddCondition("CustomField", input.CustomField, SqlOperator.Like) .AddCondition("IsExpire", input.IsExpire, SqlOperator.Equal) .AddCondition("Signature", input.Signature, SqlOperator.Like) .AddCondition("SortCode", input.SortCode, SqlOperator.Like) .AddCondition("Status", input.Status, SqlOperator.Equal) .AddCondition("CorpStatus", input.CorpStatus, SqlOperator.Equal) .AddCondition("CorpUserId", input.CorpUserId, SqlOperator.Equal) .AddCondition("UnionId", input.UnionId, SqlOperator.Equal) .AddCondition("WorkAddr", input.WorkAddr, SqlOperator.Equal) .AddCondition("SubscribeWechat", input.SubscribeWechat, SqlOperator.Equal) .AddCondition("Title", input.Title, SqlOperator.Like) .AddCondition("CurrentLoginIP", input.CurrentLoginIP, SqlOperator.Like) .AddCondition("CurrentMacAddress", input.CurrentMacAddress, SqlOperator.Like) .AddCondition("Dept_ID", input.Dept_ID, SqlOperator.Equal) .AddCondition("DeptName", input.DeptName, SqlOperator.Like) .AddCondition("CompanyName", input.CompanyName, SqlOperator.Like) .AddCondition("Company_ID", input.Company_ID, SqlOperator.Equal) .AddCondition("Editor_ID", input.Editor_ID, SqlOperator.Equal) .AddCondition("Editor", input.Editor, SqlOperator.Equal) .AddCondition("Creator_ID", input.Creator_ID, SqlOperator.Equal) .AddCondition("Creator", input.Creator, SqlOperator.Equal) .AddDateCondition("CreateTime", input.CreateTimeStart, input.CreateTimeEnd) .AddDateCondition("EditTime", input.EditTimeStart, input.EditTimeEnd) .AddDateCondition("ExpireDate", input.ExpireDateStart, input.ExpireDateEnd) .AddDateCondition("Birthday", input.BirthdayStart, input.BirthdayEnd); } return condition.BuildConditionSql().Replace("Where", ""); }
前面介紹到,我們BusinessController基類定義了常規的分頁查詢GetAll函數,如下所示。
/// <summary> /// 分頁獲取記錄 /// </summary> /// <param name="input"></param> /// <returns></returns> [HttpGet] public virtual PagedResultDto<T> GetAll([FromUri] TGetAllInput input) { var condition = GetCondition(input); var list = GetPagedData(condition, input); return list; }
其中 GetCondition 是由子類進行重寫處理,生成具體的查詢條件的。
由於這里的Sorting信息是一個字符串的排序信息,如 Name DESC或者Name ASC類似的信息,前者是字段名,后者是排序降序還是升序的標識,我們在業務里面,需要拆分一下進行組合條件,如下拆分。
//分頁查詢條件 string sortName = null; //排序字段 bool isDesc = true; if (!string.IsNullOrEmpty(input.Sorting)) { var sortInput = input as ISortedResultRequest; if (sortInput != null) { if (!string.IsNullOrWhiteSpace(sortInput.Sorting)) { List<string> strNames = sortInput.Sorting.ToDelimitedList<string>(" "); sortName = (strNames.Count > 0) ? strNames[0] : null; isDesc = sortInput.Sorting.IndexOf("desc", StringComparison.OrdinalIgnoreCase) > 0; } } }
這樣我們或者SortName,以及是否降序的判斷。
然后根據獲得分頁信息,並調用業務類的接口函數獲取對應記錄,構建為分頁所需的JSON對象返回。
//構建分頁對象 var pagerInfo = new PagerInfo() { CurrenetPageIndex = currentPage, PageSize = pageSize }; if (!string.IsNullOrWhiteSpace(sortName)) { list = baseBLL.FindWithPager(condition, pagerInfo, sortName, isDesc); } else { list = baseBLL.FindWithPager(condition, pagerInfo); } if (list != null) { foreach (var item in list) { ConvertDto(item);//對Dto部分內容進行轉義 } } //返回常用分頁對象 var result = new PagedResultDto<T> { TotalCount = totalCount, Items = list }; return result;
其中 PagedResultDto 是一個標准的分頁數據返回的對象,定義如下所示。
[Serializable] public class PagedResultDto<T> : ListResultDto<T>, IPagedResult<T> { /// <summary> /// Total count of Items. /// </summary> public int TotalCount { get; set; }
[Serializable] public class ListResultDto<T> : IListResult<T> { /// <summary> /// List of items. /// </summary> public IReadOnlyList<T> Items { get { return _items ?? (_items = new List<T>()); } set { _items = value; } } private IReadOnlyList<T> _items;
最后返回的結果集合類似如下所示:
展開單條記錄明細如下所示。
這個對象使用了Camel樣式的屬性處理,所以返回的屬性全部是Camel的格式。
/// <summary> /// 統一處理Json的格式化信息 /// </summary> public static class JsonFomatterHelper { /// <summary> /// 獲取JSON的格式化信息 /// </summary> /// <returns></returns> public static JsonMediaTypeFormatter GetFormatter() { var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter; formatter.SerializerSettings = new JsonSerializerSettings { Formatting = Formatting.Indented, ContractResolver = new CamelCasePropertyNamesContractResolver(), DateFormatHandling = DateFormatHandling.IsoDateFormat, DateFormatString = "yyyy-MM-dd HH:mm:ss", }; return formatter; } }
關於統一結果返回的封裝處理,這里采用了WrapResultAttribute進行處理,詳細可以參考我的隨筆《利用過濾器Filter和特性Attribute實現對Web API返回結果的封裝和統一異常處理》進行詳細了解。
// 重新封裝回傳格式 actionExecutedContext.Response = new HttpResponseMessage(statusCode) { Content = new ObjectContent<AjaxResponse>( new AjaxResponse(content), JsonFomatterHelper.GetFormatter()) };