编辑
2024-08-02
我当开发
00

目录

1. 数据源转DataTable 并 重置列名称
1.1 得到前台的列名和字段对照
1.2 重置数据源
2. 将重置后的数据转为byte
3. 保存文件并下载
4. 作为Gird的行为

这星期把Grid导出Excel又重写封装了一次,对于简单的导出干脆就用前台的配置列直接导出,写成了这样

[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> OnExcelDown() { //得到数据 var res = await GetData(); //导出 Grid1.OutputExcel(res); return ResultAsync(); }

重新获取数据还是有必要的,前台的数据不可信,OutputExcel 主要实现了重置列头(按前台Grid的列头),文件转为byte[],保存文件输出服务器路径;这里记录下关键路径

1. 数据源转DataTable 并 重置列名称

这里其实是两部分操作,我放在一块了,因为没有拆开的需求

1.1 得到前台的列名和字段对照

参考 FineUICoreMVC使用SignalR.InvokeAsync执行前台JS ,很容易就写出一个得到JObject键值对的方法,很优雅

C#
public static async Task<JObject> FGetColumnsAsync(this string f, string[] field = null) { field ??= new string[] { "field", "text" }; var fun = $" return F.ui['{f}'].columns.filter(m=>m.columnType=='renderfield' && m.isVisible())"; string fields = string.Join(",", field); string map = $".map(({{ {fields} }}) => ({{ {fields} }}))"; string reduce = $".reduce((acc, item) => ({{ ...acc, [item.field]: item.text }}), {{}})"; fun = fun + map + reduce + ";"; var res = await FineUIAjaxHelpEx.GetHubMessage(fun); return JObject.Parse(res) ?? new JObject(); //返回 {DataField:HeaderText} }

1.2 重置数据源

需要判断数据源的类型 是Table 还是 model;如果是model 需要兼容二级属性或多级属性

C#
public static DataTable ToDataTable(this GridAjaxHelper Grid, object dataSource, JObject Columns = null) { DataTable dataTable2 = null; if (Columns == null) Columns = Grid.GetColumnsAsync().Result; if (dataSource is DataView || dataSource is DataSet || dataSource is DataTable) { dataTable2 = TableRewrite(dataSource, Columns); } else if (dataSource is IEnumerable) { dataTable2 = ((IEnumerable)dataSource).ToDataTable(Columns); } return dataTable2; }
C#
private static DataTable TableRewrite(object dataSource, JObject Columns) { DataTable dataTable2; //将dataSource转换为DataTable if (dataSource is DataView) { dataTable2 = ((DataView)dataSource).Table; } else if (dataSource is DataSet) { dataTable2 = ((DataSet)dataSource).Tables[0]; } else { dataTable2 = (DataTable)dataSource; } //按照 Columns 将 dataTable2的列表替换 foreach (var item in Columns) { var colName = item.Value.ToString(); var col = item.Key.ToString(); if (dataTable2.Columns.Contains(col)) { dataTable2.Columns[col].ColumnName = colName; } } //按照 dataTable2的列 使用 Columns 将表替换 其他的列就删掉了 var tabalecolumns = dataTable2.Columns.Cast<DataColumn>().Select(column => column.ColumnName).ToList(); foreach (string item in tabalecolumns) { if (!Columns.Values().Contains(item)) { dataTable2.Columns.Remove(item); } } return dataTable2; }
C#
public static DataTable ToDataTable(this IEnumerable collection, JObject Columns) { DataTable dataTable = new DataTable(); //循环 Columns 的建 然后 循环 collection 使用 ObjectUtil.GetPropertyValue 找到对应的值 foreach (var item in Columns) { var colName = item.Value.ToString(); dataTable.Columns.Add(colName); } //循环 Columns 的建 然后 循环 collection 使用 ObjectUtil.GetPropertyValue 找到对应的值 foreach (var msitem in collection) { var row = dataTable.NewRow(); foreach (var item in Columns) { var colName = item.Value.ToString(); var col = item.Key.ToString(); var val = ObjectUtil.GetPropertyValue(msitem, col); row[colName] = val; } dataTable.Rows.Add(row); } return dataTable; }

注 这里 二级属性的支持就是 ObjectUtil.GetPropertyValue ObjectUtilFineUICore命名空间下 👍

2. 将重置后的数据转为byte

这里使用NPOI将DataTable转为byte[]

3. 保存文件并下载

保存文件

C#
//...略 using (FileStream fs = new FileStream(excelPath, FileMode.Create, FileAccess.Write)) { await fs.WriteAsync(data, 0, data.Length); } //excel文件保存的相对路径,提供前端下载 var relativePositioning = "/UploadFile/" + folder + "/" + excelFileName; return relativePositioning;
C#
public async Task SaveExcelAndDown(byte[] data, string excelName, string hubid = "") { //保存文件路径 var outpath = await SaveExcel(data, excelName); //转为实际路径 var uploadPath = FineUICore.PageContext.WebRootPath + outpath; string fileName = Path.GetFileName(uploadPath); //抛出js await SignalRHelper.ExecuteScript($"downloadFile('{outpath}','{fileName}');", hubid); }

这里使用js完成下载

js
downloadFile = function (url, fileName) { let link = document.createElement('a'); link.href = url; link.download = fileName; link.click(); }

4. 作为Gird的行为

因为只针对Grid控件,所以导出Exlce的应该作为Grid本身提供的一个方法,所以要扩展到Grid上

C#
public static void OutputExcel(this GridAjaxHelper Grid, object dataSource, JObject Columns = null, string title = null) { //重置数据源 var dt = Grid.ToDataTable(dataSource, Columns); //得到标题最为Excel标题 if (string.IsNullOrEmpty(title)) title = Grid.GetTitleAsync().Result; //别忘了这句 System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); var data = NPOIHelper.ExportDataTableToExcel(dt, title, "Sheet1", "1"); //得到当前的Controller var thiscontroller = Commons.PageContext.Current.GetCurrentController(); if (thiscontroller != null) { thiscontroller.SaveExcelAndDown(data, title).Wait(); } }

因为我的SaveExcelAndDown方法在BaseController中,扩展的OutputExcel方法是静态的,我又懒的写事件,所以用到了获取当前Controller实例的方法,.NetCore本身不提供该方法;其实是在过滤器中放的:

C#
/// <summary> /// 获取当前控制器 /// </summary> /// <param name="httpContext"></param> /// <returns></returns> public static BaseController GetCurrentController(this HttpContext httpContext) { if (httpContext.Items.TryGetValue("CurrentController", out var controllerObj) && controllerObj is BaseController baseController) { return controllerObj as BaseController; } return null; }
C#
public class ParameterOverwrite : ActionFilterAttribute { /// <summary> /// 回发时 /// </summary> /// <param name="filterContext"></param> public override void OnActionExecuting(ActionExecutingContext filterContext) { if (filterContext.Controller is Microsoft.AspNetCore.Mvc.Controller controller) { //把当前的Controller存起来 filterContext.HttpContext.Items["CurrentController"] = controller; } } ... }
C#
services.AddControllersWithViews().AddMvcOptions(options => { //过滤器 options.Filters.Add<ParameterOverwrite>(); }).AddRazorRuntimeCompilation();

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:没想好

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!