• 使用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,实现热更新。

  • 相关阅读:
    重新开始学习Linux
    2006:远离windows, 拥抱linux!
    适合做首页的网站: http://www.netvibes.com/
    netvibes出问题了,登陆不进去。
    推荐开源的rss阅读器rssreader 3.0,支持中文
    ubuntu的即时通讯gaim不错, 如何连qq呢?
    linux是否能把显示改为1024x768呢?
    通过IRC学习ubuntu很不错
    Firefox 扩展软件介绍(转载) Mozine 论坛
    几个有意思的网站
  • 原文地址:https://www.cnblogs.com/yuchenghao/p/12103213.html
Copyright © 2020-2023  润新知