这星期把Grid导出Excel又重写封装了一次,对于简单的导出干脆就用前台的配置列直接导出,写成了这样
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> OnExcelDown() { //得到数据 var res = await GetData(); //导出 Grid1.OutputExcel(res); return ResultAsync(); }
重新获取数据还是有必要的,前台的数据不可信,OutputExcel
主要实现了重置列头(按前台Grid
的列头),文件转为byte[]
,保存文件输出服务器路径;这里记录下关键路径
这里其实是两部分操作,我放在一块了,因为没有拆开的需求
参考 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}
}
需要判断数据源的类型 是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
ObjectUtil
在FineUICore
命名空间下 👍
这里使用NPOI
将DataTable转为byte[]
略
保存文件
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完成下载
jsdownloadFile = function (url, fileName) {
let link = document.createElement('a');
link.href = url;
link.download = fileName;
link.click();
}
因为只针对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();
本文作者:没想好
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!