• [转载]巧用Razor生成Word文档


    之前有个业务需要根据用户的数据生成一张word的报表,

    按照我之前的做法,

    1. 首先弄一个word文档,把这个word文档编辑成我所需要的表格,
    2. 然后在每个填写项内部添加word书签,
    3. 后端读取这个word,根据对应的书签进行属性插入

    这样也可以解决问题,但是有以下几个不足:

    • 需要强大的word编辑能力,能够手撸复杂的word表格
    • 书签必须和后端代码严格对应,书签的录入需要大量时间
    • 如果需要修改word,需要人为重新校验对应的书签,如果是复杂的word,书签多达到几十个甚至上百个,简直要人命
    • 面向书签编程,这不够OOP,代码无法复用。

    这种硬编码对于开发不是很友好,所以如果有一种面向对象编程,在编写过程中可以完全控制word的方法,岂不是可以减少很大的开发量?

    这里我想到的办法是:

    • 前端编写页面能力 > 开发人员的Word编辑能力,所以由前端输出和word样式一样的静态页面
    • 静态页面转Word

    那么就需要后端针对这个静态页面进行绑定操作,这里微软提供了一种技术,

    Razor 是一种允许您向网页中嵌入基于服务器的代码(Visual Basic 和 C#)的标记语法。

    只需要将静态页面转换成cshtml页面(支持C#语法的解析和编写),这个很简单;然后通过在后端接口调用Engine.Razor来操作这个cshtml页面,实现数据绑定。

    具体代码如下:

    DetailProjectDto.cs

    using RunGo.ToolsAttribute;
    using System;
    using System.ComponentModel.DataAnnotations;
     
    namespace RunGo.ProjectsManager
    {
        /// <summary>
        /// 工程详细实体
        /// </summary>
        public class DetailProjectDto
        {
            /// <summary>
            /// 主键
            /// </summary>
            public string Id { get; set; }
     
            /// <summary>
            /// 工程名称
            /// </summary>
            [StringLength(250)]
            [Required]
            [Export("工程名称")]
            public string ProjectName { get; set; }
     
            /// <summary> /// 工程编号
            /// </summary>
            [StringLength(250)]
            [Required]
            [Export("工程编号")]
            public string ProjectNo { get; set; }  }
    }

    test.cshtml

    @using Microsoft.AspNetCore.Html;
    @using RunGo.ProjectsManager;
        <h2 style="text-align:center;font-family: STSong;">工程基本信息</h2>
        <table style="table-layout: fixed;word-break: break-all;border: 1px solid #000000;border-collapse: collapse;">
     
            <tr style="height: 60px;font-size: 12px;">
         <td style="border: 1px solid #000000;border-collapse: collapse;font-family: STSong;text-align: center; font-size:14px;font-weight:600;" colspan="2">工程名称</td>
        <td style="border: 1px solid #000000;border-collapse: collapse;font-family: STSong;text-align: center;">
                    @Model.ProjectName
                </td>
         <td style="border: 1px solid #000000;border-collapse: collapse;font-family: STSong;text-align: center; font-size:14px;font-weight:600;" colspan="2">工程编号</td>
          <td style="border: 1px solid #000000;border-collapse: collapse;font-family: STSong;text-align: center;">
                 @Model.ProjectNo
                </td>
            </tr>
        </table>
    

      

    接口

        注:Nuget需要引入RazorEngine.NetCore

    using RazorEngine;
    using RazorEngine.Templating;
    
    /// <summary>
            /// 工程基本信息报告下载
            /// </summary>
            /// <param name="projectId">工程id</param>
            /// <returns></returns>
            [HttpGet]
            public FileResult UploadProjectBaseInfo(string projectId)
            {
                string memi = string.Empty;
                Stream outData = null;
                outData = GetBaseInfo(projectId, out memi);
                return File(outData, memi, $"工程基本信息.docx");
            }
            /// <summary>
            /// 工程基本信息
            /// </summary>
            /// <param name="projectId"></param>
            /// <param name="memi"></param>
            /// <returns></returns>
            [RemoteService(false)]
            public Stream GetBaseInfo(string projectId, out string memi)
            {
                var model = _projectManagerService.Detail(projectId).Result;
                if (model == null)
                {
                    throw new Exception("");
                }
                var template = System.IO.File.ReadAllText($"{_hostingEnvironment.WebRootPath}\test.cshtml");
                var html = Engine.Razor.RunCompile(template, Guid.NewGuid().ToString(), typeof(DetailProjectDto), model);
                var op = _SpireDocHelper.SwaggerHtmlConvers(html, ".docx", out memi);
                if (op == null)
                    throw new Exception("转换失败");
                return op;
            }

    相关工具方法:

        注:Nuget需要引入 Spire.Doc

      public Stream SwaggerHtmlConvers(string html, string type, out string memi)
           {
               string fileName = Guid.NewGuid().ToString() + type;
               string webRootPath = _hostingEnvironment.WebRootPath;
               string path = webRootPath + @"FilesTempFiles";
               var addrUrl = path + $"{fileName}";
               FileStream fileStream = null;
               var provider = new FileExtensionContentTypeProvider();
               memi = provider.Mappings[type];
               try
               {
                   if (!Directory.Exists(path))
                   {
                       Directory.CreateDirectory(path);
                   }
                   var data = System.Text.Encoding.Default.GetBytes(html);
                   var stream = new MemoryStream(data);
                   //创建Document实例
                   Document document = new Document();
                   //加载HTML文档
                   document.LoadFromStream(stream, FileFormat.Html, XHTMLValidationType.None);
                   document.SaveToFile(addrUrl, FileFormat.Docx);
     
                   document.Close();
                   fileStream = System.IO.File.Open(addrUrl, FileMode.OpenOrCreate);
                   var filedata = ByteHelper.StreamToBytes(fileStream);
                   var outdata = ByteHelper.BytesToStream(filedata);
                   return outdata;
               }
               catch (Exception e)
               {
                   return null;
               }
               finally
               {
                   if (fileStream != null)
                       fileStream.Close();
                   if (System.IO.File.Exists(addrUrl))
                       System.IO.File.Delete(addrUrl);//删掉文件
               }
           }
    public static byte[] StreamToBytes(Stream stream)
     
           {
               byte[] bytes = new byte[stream.Length];
     
               stream.Read(bytes, 0, bytes.Length);
     
               // 设置当前流的位置为流的开始
     
               stream.Seek(0, SeekOrigin.Begin);
     
               return bytes;
           }
     
           /// 将 byte[] 转成 Stream
     
           public static Stream BytesToStream(byte[] bytes)
     
           {
               Stream stream = new MemoryStream(bytes);
     
               return stream;
           }

    总结:

    这样可以有效利用前端开发编写页面的速度远大于操作word书签的速度,使得后端开发只需针对完成后的页面进行实体绑定即可生成Word,大大提高了开发效率,接口成型之后,只需要提供cshtml文件以及实体即可实现绑定;

    甚至可以将对应的cshtml路径以及对应的实体写入配置文件,通过反射来控制接口生成的Word,实现热更新。

  • 相关阅读:
    下载文件乱码问题的解决
    Jquery Ajax实例(一)
    JQuery实现checkbox的全选/取消全选,实现类似于邮箱功能
    C#重写ToString方法实例
    生成不重复的随机数
    List<T>清除重复某一项
    Ubuntu16.04安装有道词典
    Ubuntu16.04 VTK7.1.0+QT4.8.6+QtCreator开发环境配置
    MATLAB求解代数方程、微分方程的一些常用指令
    读取Simulink中Dataset类型的数据
  • 原文地址:https://www.cnblogs.com/cyqdeshenluo/p/12123433.html
Copyright © 2020-2023  润新知