1 System.Linq.AsyncIEnumerableExtensions (Data\Extensions\AsyncIEnumerableExtensions.cs)
namespace System.Linq
{
/// <summary>
/// 【异步枚举数扩展--类】
/// <remarks>
/// 摘要:
/// 该类通过对System.Linq.Async中方法的自定义扩展,实现Linq中的Select、Where、Any等操作方法的异步实现,并通过自定义的异步Linq方法,进行求和、求累加、求总计和编组等操作。
/// </remarks>
/// </summary>
public static class AsyncIEnumerableExtensions
{
/// <summary>
/// 【等待选择】
/// <typeparam name="TSource">泛型类型实例(1个指定类的类型实例,数据源)。</typeparam>
/// <typeparam name="TResult">泛型类型实例(通过“System.Linq.Async”操作筛选出符合条件的实例)。</typeparam>
/// <param name="source">以“IEnumerable”实例进行存储的1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)。</param>
/// <param name="predicate">一个具有返回值的异步委托方法实例,方法实例用于从数据源中通过“System.Linq.Async”操作筛选出符合条件的实例。</param>
/// <remarks>
/// 摘要:
/// 从数据源中通过“System.Linq.Async”操作筛选出所有符合条件的实例,并把这些实例存储到“IAsyncEnumerable”实例中。
/// </remarks>
/// <returns>
/// “IAsyncEnumerable”实例,该实例存储着通过“System.Linq.Async”操作筛选出所有符合条件的实例。
/// </returns>
/// </summary>
public static IAsyncEnumerable<TResult> SelectAwait<TSource, TResult>(this IEnumerable<TSource> source,
Func<TSource, ValueTask<TResult>> predicate)
{
return source.ToAsyncEnumerable().SelectAwait(predicate);
}
}
}
2 Services.Customers.Caching.CustomerCacheEventConsumer
uusing Core.Domain.Customers;
using Services.Caching;
namespace Services.Customers.Caching
{
/// <summary>
/// 摘要:
/// 通过该类中的方法成员,在用户实体的1个实例执行插入、更新或持久化/逻辑删除操作后,为该实体所有相关实例的分布式缓存的强制移除操作提供数据支撑,同时避免用户实体的列表渲染显示出现的异常。
/// </summary>
public class CustomerCacheEventConsumer : CacheEventConsumer<Customer>
{
}
}
3 重构Services.Customers.CustomerService.GetAllCustomersAsync
/// <param name="deleted">指示是否包含指定实体中所有的逻辑删除项,默认值:null,即该参数实例不参与筛选操作。</param>
/// <param name="email">1个指定的电字邮箱字符串,默认值:null,即该参数实例不参与筛选操作。</param>
/// <param name="username">1个指定的用户名/昵称,默认值:null,即该参数实例不参与筛选操作。</param>
/// <param name="phone">1个指定的手机号,默认值:null,即该参数实例不参与筛选操作。</param>
/// <param name="createdDateFrom">1个指定的用户注册开始日期,默认值:null,即该参数实例不参与筛选操作。</param>
/// <param name="createdDateTo">>1个指定的用户注册结束日期,默认值:null,即该参数实例不参与筛选操作。</param>
/// <param name="orderByFiled">1个指定的用于排序操作的排序字段字符串,默认值:null,即按照创建时间以倒序方式进行排序操作。</param>
/// <param name="orderByType">1个指定的用于排序操作的排序排序方式,默认值:null,即按照创建时间以倒序方式进行排序操作。</param>
/// <param name="pageIndex">当前页的页数值(与“pageSize”结合,设定需要跳过指定实体实例的个数值),默认值:0,即不需要执行跳过操作。</param>
/// <param name="pageSize">分页操作中每页最多显示实例的项(行)数值(与“pageIndex”结合,设定需要跳过指定实体实例的个数值),默认值:int.MaxValue=2147483647。</param>
/// <param name="getOnlyTotalCount">指示数据源中否包含有实例,默认值:null,即该参数实例不参与筛选操作。</param>
/// <summary>
/// 【异步获取所有用户】
/// <remarks>
/// 摘要:
/// 根据前端分页组件传递的参数实例,获取符合条件的用户表1指定页面内的持久化数据加载到1指定内存逻辑页面内。
/// 说明:
/// 该方法没有定义缓存操作,更没有定义缓存的移除操作。
/// </remarks>
/// <returns>
/// 返回:
/// 1指定内存逻辑页面内的用户实体的所有实例。
/// </returns>
/// </summary>
public virtual async Task<IPagedList<Customer>> GetAllCustomersAsync(bool? deleted = null, string email = null, string username = null, string phone = null,
DateTime? createdDateFrom = null, DateTime? createdDateTo = null, string orderByFiled = null, string orderByType = null,
long pageIndex = 0, int pageSize = int.MaxValue, bool getOnlyTotalCount = false)
{
var customers = await _customerRepository.GetAllPagedAsync(query =>
{
//根据参数实例,执行筛选操作。
if (deleted.HasValue)
query = query.Where(c => c.Deleted.Equals(deleted));
if (createdDateFrom.HasValue)
query = query.Where(c => createdDateFrom.Value <= c.CreatedDate);
if (createdDateTo.HasValue)
query = query.Where(c => createdDateTo.Value >= c.CreatedDate);
if (!string.IsNullOrWhiteSpace(email))
query = query.Where(c => c.Email.Contains(email));
if (!string.IsNullOrWhiteSpace(username))
query = query.Where(c => c.Username.Contains(username));
if (!string.IsNullOrWhiteSpace(phone))
query = query.Where(c => c.Phone.Contains(phone));
//根据参数实例,执行排序操作。
if (string.IsNullOrWhiteSpace(orderByFiled) && string.IsNullOrWhiteSpace(orderByType))
{
query = query.OrderByDescending(customer => customer.CreatedDate);
}
else if (!string.IsNullOrWhiteSpace(orderByFiled))
{
if (string.IsNullOrEmpty(orderByType) || orderByType.Equals("asc", StringComparison.InvariantCultureIgnoreCase))
{
query = query.OrderBy(orderByFiled);
}
else if (orderByType.Equals("desc", StringComparison.InvariantCultureIgnoreCase))
{
query = query.OrderByDescending(orderByFiled);
}
}
return query;
}, pageIndex, pageSize, getOnlyTotalCount);
return customers;
}
4 Services.Customers.CustomerService.GetCustomerByIdAsync
/// <param name="customerId">用户实体1个指定实例的长整型编号值。</param>
/// <summary>
/// 【异步通过编号值获取用户】
/// <remarks>
/// 摘要:
/// 直接从用户表中获用户实体的1个指定实例;或从分布式缓存数据库获取用户实体的1个指定实例(即使该实例处于逻辑删除状态,也获取该实例)。
/// </remarks>
/// <returns>
/// 返回:
/// 用户实体的1个指定实例。
/// </returns>
/// </summary>
public virtual async Task<Customer> GetCustomerByIdAsync(long customerId)
{
return await _customerRepository.GetByIdAsync(customerId,
cache => cache.PrepareKeyForShortTermCache(EntityCacheDefaults<Customer>.ByIdCacheKey, customerId));
}
5 Web.Areas.Admin.Factories.CustomerModelFactory.PrepareAvatarUrlAsync
/// <param name="customerId">1个指定的长整型值。</param>
/// <summary>
/// 【异步预处理头像图片】
/// </summary>
/// <remarks>
/// 摘要:
/// 获取1个指定用户头像图片的网络格式绝对路径字符串。
/// </remarks>
/// <returns>
/// 1个指定用户头像图片的网络格式绝对路径字符串。
/// </returns>
public async Task<string> PrepareAvatarUrlAsync(long customerId)
{
string _avatarUrl = string.Empty;
Customer _customer = await _customerService.GetCustomerByIdAsync(customerId);
if (_customer != null)
{
if (string.IsNullOrEmpty(_customer.Avatar))
{
string _path = _nopFileProvider.Combine(_nopFileProvider.WebRootPath, @"\images\Avatar\Default.jpg");
if (_nopFileProvider.FileExists(_path))
{
string _absoluteAvatarUrl = _nopFileProvider.GetVirtualPath(@"\images\Avatar\Default.jpg");
//去URL格式路径字符中的第一个字符:“~/”。
_absoluteAvatarUrl = _absoluteAvatarUrl.Replace("~/", string.Empty);
_avatarUrl = _webHelper.GetStoreHost() + _absoluteAvatarUrl;
}
}
else
{
_avatarUrl = _webHelper.GetStoreHost().TrimEnd('/') + _customer.Avatar;
}
}
return _avatarUrl;
}
6 重构Web.Areas.Admin.Factories.CustomerModelFactory.PrepareCustomerListModelAsync
/// <param name="searchModel">用户过滤筛选模型纪录的1个指定实例。</param>
/// <summary>
/// 【异步预处理用户分页列表模型纪录】
/// <remarks>
/// 摘要:
/// 获取用户分页列表模型纪录的1个指定实例,为Jquery DataTable插件当前页渲染显示提供基本且必须的数据支撑。
/// </remarks>
/// <returns>
/// 用户分页列表模型纪录的1个指定实例。
/// </returns>
/// </summary>
public virtual async Task<CustomerListModel> PrepareCustomerListModelAsync(CustomerSearchModel searchModel)
{
//根据前端传递的用户过滤筛选模型纪录的1个指定实例,从用户表中获取(1逻辑页中的)相应行数的数据,并把这些数据存储到列表实例中。
var customers = await _customerService.GetAllCustomersAsync(
orderByFiled: searchModel.OrderBy, orderByType: searchModel.OrderDir.ToString(), pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize);
//实例化当前用户分页列表模型纪录(“物理页”),为Jquery DataTable插件当前页渲染显示提供基本且必须的数据支撑。
var model = await new CustomerListModel().PrepareToGridAsync(searchModel, customers, () =>
{
return customers.SelectAwait(async customer =>
{
var customerModel = customer.ToModel<CustomerModel>();
customerModel.Avatar = await PrepareAvatarUrlAsync(customerModel.Id);
return customerModel;
});
});
return model;
}
7 Web\Areas\Admin\Views\Customer\Index.cshtml
<div class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<div class="fs-3">用户列表</div>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-end">
<li class="breadcrumb-item">
@*resetRoleList:用于控制对弹出框的关闭和角色列表的刷新渲染*@
<button type="button" id="resetRoleList" class="btn btn-secondary me-3 d-none">
<i class="fa-solid fa-rotate-right"></i>
重置
</button>
<button type="button" onclick="OpenWindow('@(Url.Action("Create", "Role", new {btnId = "resetRoleList"}))', 500, 500, true); return false;" class="btn btn-success float-end">
<i class="fas fa-plus"></i>
添加
</button>
</li>
</ol>
</div>
</div>
</div>
</div>
<!-- Main content -->
<div class="content">
<div class="container-fluid">
<div class="row">
<div class="col-12">
<!-- Default box -->
<div class="card">
<div class="card-body">
<table id="example" class="table table-striped table-bordered nowrap projects" width="100%" cellspacing="0">
</table>
</div>
<!-- /.card-body -->
</div>
<!-- /.card -->
</div>
</div>
<!-- /.row -->
</div><!-- /.container-fluid -->
</div>
@section Scripts {
<script type="text/javascript">
var tableModel;
$(document).ready(function () {
tableModel = $("#example").DataTable({
language: {
url: '../../lib/DataTables/DataTables-1.13.4/language/zh.json' //DataTables语言配置选项
},
processing: true, //是否显示“处理中...”(排序的时候,数据很多耗费时间长的话,也会显示这个)
serverSide: true, //是否开启服务器模式
paging: true, //是否允许翻页
lengthMenu: [10, 20, 50, 100], //Jquery DataTable插件下拉框控件中的数量选择内容。
pageLength: 10, // 每页的行数的默认值:10。
searching: false, //是否在Jquery DataTable插件中显示搜索框控件。
//autoWidth: true, //自适应宽度
bSort: true, //显示排序按钮
pagingType: "numbers",//使用该类型,为在“表中数据为空”时,禁止在Jquery DataTable插件中,显示“上页”、“下页”按钮。
//当处理大数据时,延迟渲染数据,有效提高Datatables处理能力
deferRender: true,
order: [[8, "desc"]],//默认设置第8列以“倒序”方式对Jquery DataTable插件进行排序显示。
ajax: {
url: "/Admin/Customer/CustomerList",
type: "POST",
datatype: "JSON",
cache: false, //禁用缓存
},
columnDefs: [{ //设置列定义初始化属性
defaultContent: "", //为列设置默认的静态内容
targets: "_all" //指定所有列
}],
//注意:"data"对应的字段(实体属性)的第1个字母必是小写,否则实体实例的值将不会被浏览器渲染出来。
columns: [
{
data: "id", "title": "<input type='checkbox' id='checkAll' class='form-check-input checkBox20' />",
width: "25px",
orderable: false,//禁止当前列渲染显示排序按钮。
render: function (data, type, row, meta) {
return "<input type='checkbox' name='checkItem' value=" + data + " class='form-check-input checkBox20' />";
},
},
{ data: "id", title: "编号", width: "100px" },
{ data: "username", title: "用户名", autoWidth: true },
{
data: null,
title: "头像",
width: "65px",
orderable: false,//禁止当前列渲染显示排序按钮。
render: function (data, type, full, meta) {
return "<img src='" + data.avatar + "'class='img-size-64'/>";
},
},
{ data: "email", title: "邮箱", autoWidth: true },
{ data: "phone", title: "手机", width: "100px", orderable: false },
{
data: "active",
title: "可用",
width: "50px",
orderable: false,
//禁止当前列渲染显示排序按钮。
render: function (data, type, row, full, meta) {
//console.log(data);
if (data) {
return "<span class='badge bg-success'>激活</span>";
}
else {
return "<span class='badge bg-danger'>禁用</span>";
}
},
},
{
data: "deleted",
title: "删除",
width: "50px",
orderable: false,//禁止当前列渲染显示排序按钮。
render: function (data, type, row, full, meta) {
//console.log(data);
if (data)
{
return "<span class='badge bg-danger'>已经删除</span>";
}
else
{
return "<span class='badge bg-success' style='letter-spacing: 8px; text-indent: 8px; '>可用</span>";
}
},
},
{
data: "createdDate",
title: "创建日期",
width: "100px",
render: function (data, type, full, meta) {
//console.log(data);
//console.log(type);
//console.log(full);
//console.log(meta);
//时间格式化
return moment(data).format("YYYY-MM-DD HH:mm:ss");
}
},
{
data: null,
width: "100px",
orderable: false,//禁止当前列渲染显示排序按钮。
render: function (data, type, row, full, meta) {
return "<a href='#' class='btn btn-sm btn-info me-3' >编辑</a>"
+ "<a href='#' class='btn btn-sm btn-danger' οnclick=deleteData('" + data.id + "'); >删除</a>";
},
},
]
});
});
</script>
}
230528_018ShopRazor(JQuery DataTables初始化渲染显示与排序)。