.NET 6中实现DataTable服务端过滤及获取HttpContext的方案咨询
.NET 6 迁移中 HttpContext 为空问题的解决方法
问题根源
- API 体系差异:.NET Framework 的
System.Web.HttpContext和 .NET 6 的Microsoft.AspNetCore.Http.HttpContext是完全独立的实现,无法直接兼容。 - 静态方法设计缺陷:原静态
FilterTable方法直接依赖请求上下文,而静态方法是全局无状态的,HttpContext 是请求级别的资源,在异步操作、后台线程等场景下极易出现为空的情况。 - 注入时机问题:即便注册了
IHttpContextAccessor,如果在请求管道外调用静态方法,也会获取不到有效上下文。
最优解决方案:解耦上下文依赖
直接将方法需要的 bSearchable_* 参数从 HttpContext 中提取后传入方法,彻底消除对请求上下文的依赖,同时避免线程安全问题。
步骤1:控制器中提取请求参数
在调用过滤方法前,先从请求中收集所有 bSearchable 标记:
[HttpPost] public IActionResult GetFilteredData(JQPM p) { // 假设从数据源获取原始DataTable DataTable rawDt = GetRawDataTable(); int totalCount = 0; // 收集前端传入的bSearchable参数列表 var searchableFlags = new List<bool>(); var columnCount = p.sColumns.Split(',').Length; for (int j = 0; j < columnCount; j++) { bool isSearchable = bool.TryParse(Request.Form["bSearchable_" + j], out var result) ? result : false; searchableFlags.Add(isSearchable); } // 调用重构后的过滤方法 var filteredDt = FilterTable(rawDt, ref totalCount, p, searchableFlags); return Json(new { data = filteredDt, recordsTotal = totalCount }); }
步骤2:重构 FilterTable 方法
移除静态修饰(改为实例方法或保留静态但传入参数),不再直接读取 HttpContext:
public DataTable FilterTable(DataTable dt, ref int totalCount, JQPM p, List<bool> searchableFlags) { p.sColumnslist = p.sColumns.Split(','); totalCount = dt.Rows.Count; if (!string.IsNullOrEmpty(p.sSearch)) { // 拆分搜索关键词并过滤空值 var searchTerms = p.sSearch.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var term in searchTerms) { // 用Linq替代字符串拼接查询,避免SQL注入 var filteredRows = dt.AsEnumerable().Where(row => { for (int j = 0; j < p.sColumnslist.Length; j++) { if (!searchableFlags[j]) continue; var column = dt.Columns[p.sColumnslist[j]]; var cellValue = row[column]; if (cellValue == DBNull.Value) continue; // 根据列类型匹配关键词 if (column.DataType == typeof(string)) { if (((string)cellValue).Contains(term, StringComparison.OrdinalIgnoreCase)) return true; } else if (column.DataType == typeof(DateTime)) { if (((DateTime)cellValue).ToString("yyyy-MM-dd HH:mm:ss").Contains(term)) return true; } else if (column.DataType == typeof(int)) { if (int.TryParse(term, out var num) && (int)cellValue == num) return true; } } return false; }); dt = filteredRows.CopyToDataTable(); totalCount = dt.Rows.Count; } } // 执行分页逻辑(对应原GetDatatable方法) dt = ApplyPagination(dt, ref totalCount, p); return dt; } // 分页逻辑示例 private DataTable ApplyPagination(DataTable dt, ref int totalCount, JQPM p) { var startIndex = p.iDisplayStart; var pageSize = p.iDisplayLength; if (startIndex + pageSize > dt.Rows.Count) pageSize = dt.Rows.Count - startIndex; var paginatedDt = dt.Clone(); for (int i = startIndex; i < startIndex + pageSize; i++) { paginatedDt.ImportRow(dt.Rows[i]); } return paginatedDt; }
备选方案:正确使用 IHttpContextAccessor
如果必须保留上下文依赖,需确保注入时机和调用场景正确:
1. 注册 HttpContextAccessor
在 Program.cs 中添加服务注册:
builder.Services.AddHttpContextAccessor();
2. 创建过滤服务类
将方法改为实例方法,通过构造注入获取 IHttpContextAccessor:
public class TableFilterService { private readonly IHttpContextAccessor _httpContextAccessor; public TableFilterService(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public DataTable FilterTable(DataTable dt, ref int totalCount, JQPM p) { var request = _httpContextAccessor.HttpContext?.Request; if (request == null) throw new InvalidOperationException("当前无有效请求上下文"); p.sColumnslist = p.sColumns.Split(','); totalCount = dt.Rows.Count; if (!string.IsNullOrEmpty(p.sSearch)) { var searchTerms = p.sSearch.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries); foreach (var term in searchTerms) { string filterExpr = "0=1 "; for (int j = 0; j < p.sColumnslist.Length; j++) { bool isSearchable = bool.TryParse(request.Form["bSearchable_" + j], out var result) ? result : false; if (!isSearchable) continue; var column = dt.Columns[p.sColumnslist[j]]; if (column.DataType == typeof(string)) { filterExpr += $" or [{column.ColumnName}] LIKE '%{term}%'"; } else if (column.DataType == typeof(DateTime)) { filterExpr += $" or CONVERT(varchar, [{column.ColumnName}], 120) LIKE '%{term}%'"; } else if (column.DataType == typeof(int) && int.TryParse(term, out var num)) { filterExpr += $" or [{column.ColumnName}] = {num}"; } } dt = dt.Select(filterExpr).CopyToDataTable(); totalCount = dt.Rows.Count; } } dt = ApplyPagination(dt, ref totalCount, p); return dt; } }
3. 注册并使用服务
在 Program.cs 注册服务:
builder.Services.AddScoped<TableFilterService>();
在控制器中注入使用:
public class DataController : ControllerBase { private readonly TableFilterService _filterService; public DataController(TableFilterService filterService) { _filterService = filterService; } [HttpPost] public IActionResult GetFilteredData(JQPM p) { DataTable rawDt = GetRawDataTable(); int totalCount = 0; var filteredDt = _filterService.FilterTable(rawDt, ref totalCount, p); return Json(new { data = filteredDt, recordsTotal = totalCount }); } }
关键注意事项
- 避免SQL注入:原代码直接拼接字符串到查询表达式中存在严重安全风险,建议用Linq to DataTable替代。
- 静态方法慎用:静态方法不应依赖请求上下文这类请求级资源,否则会引发线程安全问题和上下文为空异常。
- 上下文有效性:使用
IHttpContextAccessor时,必须确保在请求管道内调用方法(避免在后台任务、异步回调中使用)。
内容的提问来源于stack exchange,提问作者Ahsan Raza
相关产品推荐
相关产品推荐

