利用查询条件对象,在Asp.net Web API中实现对业务数据的分页查询处理
在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()) };