Office Online Server是微软开发的一套基于Office实现在线文档预览编辑的技术框架(支持当前主流的浏览器,且浏览器上无需安装任何插件,支持word、excel、ppt、pdf等文档格式),其客户端通过WebApi方式可集成到自已的应用中,支持Java、C#等语言。Office Online Server原名为:Office Web Apps Server(简称OWAS)。因为近期有ASP.NET Core 2.0的项目中要实现在线文档预览与编辑,就想着将Office Online Server集成到项目中来,通过网上查找,发现大部分的客户端的实现都是基于ASP.NET的,而我在实现到ASP.NET Core 2.0的过程中也遇到了不少的问题,所以就有了今天这篇文章。

安装Office Online Server

微软的东西在安装上都是很简单的,下载安装包一路”下一步“就可完成。也可参考如下说明来进行安装:https://docs.microsoft.com/zh-cn/officeonlineserver/deploy-office-online-server

完成安装后会在服务器上的IIS上自动创建两个网站,分别为:HTTP80、HTTP809。其中HTTP80站绑定80、443端口,HTTP809站绑定809、810端口。

业务关系

1、Office Online Server服务端(WOPI Server),安装在服务器上用于受理来自客户端的预览、编辑请求等。服务端很吃内存的,单机一定不能低于8G内存。

2、Office Online Server客户端(WOPI Client),这里因为集成在了自已的项目中,所以Office Online Server客户端也就是自已的项目中的子系统。

用户通过项目中的业务系统请求客户端并发起对某一文档的预览或编辑请求,客户端接受请求后再通过调用服务端的WebApi完成一系列约定通讯后,服务端在线输出文档并完成预览与编辑功能。

实现原理

可通过如下图(图片来自互联网)能清晰的看出浏览器、Office Online Server服务端、Office Online Server客户端之间的交互顺序与关系。在这过程中,Office Online Server客户端需自行生成Token及身份验证,这也是为保障Office Online Server客户端的安全手段。

实现代码

客户端编写拦截器,拦截器中主要接受来自服务端的请求,并根据服务端的请求类型做出相应动作,请求类型包含如下几种:CheckFileInfo、GetFile、Lock、GetLock、RefreshLock、Unlock、UnlockAndRelock、PutFile、PutRelativeFile、RenameFile、DeleteFile、PutUserInfo等。具体代码如下:

 using Microsoft.AspNetCore.Http;
 using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Web;
 //编写一个处理WOPI请求的客户端拦截器
 namespace Lezhima.Wopi.Base
 {
     public class ContentProvider
     {
         //声明请求代理
         private readonly RequestDelegate _nextDelegate;

         public ContentProvider(RequestDelegate nextDelegate)
         {
             _nextDelegate = nextDelegate;
         }

         //拉截并接受所有请求
         public async Task Invoke(HttpContext context)
         {
 		//判断是否为来自WOPI服务端的请求
             if (context.Request.Path.ToString().ToLower().IndexOf("files") >= 0)
             {
                 WopiRequest requestData = ParseRequest(context.Request);

                 switch (requestData.Type)
                 {
 			//获取文件信息
                     case RequestType.CheckFileInfo:
                         await HandleCheckFileInfoRequest(context, requestData);
                         break;

                     //尝试解锁并重新锁定
                     case RequestType.UnlockAndRelock:
                         HandleUnlockAndRelockRequest(context, requestData);
                         break;

                     //获取文件
                     case RequestType.GetFile:
                         await HandleGetFileRequest(context, requestData);
                         break;

                     //写入文件
                     case RequestType.PutFile:
                         await HandlePutFileRequest(context, requestData);
                         break;

                     default:
                         ReturnServerError(context.Response);
                         break;
                 }
             }
             else
             {
                 await _nextDelegate.Invoke(context);
             }
         }

         /// <summary>
         /// 接受并处理获取文件信息的请求
         /// </summary>
         /// <remarks>
         /// </remarks>
         private async Task HandleCheckFileInfoRequest(HttpContext context, WopiRequest requestData)
         {
 		//判断是否有合法token
             if (!ValidateAccess(requestData, writeAccessRequired: false))
             {
                 ReturnInvalidToken(context.Response);
                 return;
             }
             //获取文件
             IFileStorage storage = FileStorageFactory.CreateFileStorage();
             DateTime? lastModifiedTime = DateTime.Now;
             try
             {
                 CheckFileInfoResponse responseData = new CheckFileInfoResponse()
                 {
 			//获取文件名称
                     BaseFileName = Path.GetFileName(requestData.Id),
                     Size = Convert.ToInt32(size),
                     Version = Convert.ToDateTime((DateTime)lastModifiedTime).ToFileTimeUtc().ToString(),
                     SupportsLocks = true,
                     SupportsUpdate = true,
                     UserCanNotWriteRelative = true,

                     ReadOnly = false,
                     UserCanWrite = true
                 };

                 var jsonString = JsonConvert.SerializeObject(responseData);

                 ReturnSuccess(context.Response);

                 await context.Response.WriteAsync(jsonString);

             }
             catch (UnauthorizedAccessException ex)
             {
                 ReturnFileUnknown(context.Response);
             }
         }

         /// <summary>
         /// 接受并处理获取文件的请求
         /// </summary>
         /// <remarks>
         /// </remarks>
         private async Task HandleGetFileRequest(HttpContext context, WopiRequest requestData)
         {
      	//判断是否有合法token
             if (!ValidateAccess(requestData, writeAccessRequired: false))
             {
                 ReturnInvalidToken(context.Response);
                 return;
             }

             //获取文件
             var stream = await storage.GetFile(requestData.FileId);

             if (null == stream)
             {
                 ReturnFileUnknown(context.Response);
                 return;
             }

             try
             {
                 int i = 0;
                 List<byte> bytes = new List<byte>();
                 do
                 {
                     byte[] buffer = new byte[1024];
                     i = stream.Read(buffer, 0, 1024);
                     if (i > 0)
                     {
                         byte[] data = new byte[i];
                         Array.Copy(buffer, data, i);
                         bytes.AddRange(data);
                     }
                 }
                 while (i > 0);

                 ReturnSuccess(context.Response);
 		    await context.Response.Body.WriteAsync(bytes, bytes.Count);

             }
             catch (UnauthorizedAccessException)
             {
                 ReturnFileUnknown(context.Response);
             }
             catch (FileNotFoundException ex)
             {
                 ReturnFileUnknown(context.Response);
             }

         }

         /// <summary>
         /// 接受并处理写入文件的请求
         /// </summary>
         /// <remarks>
         /// </remarks>
         private async Task HandlePutFileRequest(HttpContext context, WopiRequest requestData)
         {
 		//判断是否有合法token
             if (!ValidateAccess(requestData, writeAccessRequired: true))
             {
                 ReturnInvalidToken(context.Response);
                 return;
             }

             try
             {
                 //写入文件
                 int result = await storage.UploadFile(requestData.FileId, context.Request.Body);
                 if (result != 0)
                 {
                     ReturnServerError(context.Response);
                     return;
                 }

                 ReturnSuccess(context.Response);
             }
             catch (UnauthorizedAccessException)
             {
                 ReturnFileUnknown(context.Response);
             }
             catch (IOException ex)
             {
                 ReturnServerError(context.Response);
             }
         }

         private static void ReturnServerError(HttpResponse response)
         {
             ReturnStatus(response, 500, "Server Error");
         }

     }
 }

拦截器有了后,再到Startup.cs文件中注入即可,具体代码如下:

         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
         {
             if (env.IsDevelopment())
             {
                 app.UseDeveloperExceptionPage();
                 app.UseBrowserLink();
             }
             else
             {
                 app.UseExceptionHandler("/Home/Error");
             }

             app.UseStaticFiles();
             app.UseAuthentication();

 	        //注入中间件拦截器,这是将咱们写的那个Wopi客户端拦截器注入进来
             app.UseMiddleware<ContentProvider>();

             app.UseMvc(routes =>
             {
                 routes.MapRoute(
                     name: "default",
                     template: "{controller=Home}/{action=Index}/{name?}");
             });
         }

至止,整个基于Office Online Server技术框架在ASP.NET Core上的文档预览/编辑功能就完成了。够简单的吧!!

总结

1、Office Online Server服务端建议在服务器上独立部署,不要与其它业务系统混合部署。因为这货实在是太能吃内存了,其内部用了WebCached缓存机制是导致内存增高的一个因素。

2、Office Online Server很多资料上要求要用AD域,但我实际在集成客户端时没有涉及到这块,也就是说服务端是开放的,但客户端是通过自行颁发的Token与验证来保障安全的。

3、利用编写中间件拦截器,并在Startup.cs文件中注入中间件的方式来截获来自WOPI服务端的所有请求,并对不同的请求类型做出相应的处理。

声明

本文为作者原创,转载请备注出处与保留原文地址,谢谢。如文章能给您带来帮助,请点下推荐或关注,感谢您的支持!

ASP.NET Core2集成Office Online Server(OWAS)实现办公文档的在线预览与编辑(支持word\excel\ppt\pdf等格式)的更多相关文章

  1. [转发]ASP.NET Core2集成Office Online Server(OWAS)实现办公文档的在线预览与编辑(支持word\excel\ppt\pdf等格式)

    转载自:https://www.cnblogs.com/Andre/p/9549874.html Office Online Server是微软开发的一套基于Office实现在线文档预览编辑的技术框架 ...

  2. Java实现office文档与pdf文档的在线预览功能

    最近项目有个需求要java实现office文档与pdf文档的在线预览功能,刚刚接到的时候就觉得有点难,以自己的水平难以在三四天做完.压力略大.后面查找百度资料.以及在同事与网友的帮助下,四天多把它做完 ...

  3. Atitit.office word  excel  ppt pdf 的web在线预览方案与html转换方案 attilax 总结

    Atitit.office word  excel  ppt pdf 的web在线预览方案与html转换方案 attilax 总结 1. office word  excel pdf 的web预览要求 ...

  4. 怎么在线预览.doc,.docx,.ofd,.pdf,.wps,.cad文件以及Office文档的在线解析方式。

    前言 Office文件在线预览是目前移动化办公的一种新趋势.Office在线预览指的是Office系列的文件在线查看而不依附域客户端的存在.在浏览器或者浏览器控件中可以预览查看Word.PDF.Exc ...

  5. apache poi操作office文档----java在线预览txt、word、ppt、execel,pdf代码

    在页面上显示各种文档中的内容.在servlet中的逻辑 word: BufferedInputStream bis = null;  URL url = null;  HttpURLConnectio ...

  6. 基于开源方案构建统一的文件在线预览与office协同编辑平台的架构与实现历程

    大家好,又见面了. 在构建业务系统的时候,经常会涉及到对附件的支持,继而又会引申出对附件在线预览.在线编辑.多人协同编辑等种种能力的诉求. 对于人力不是特别充裕.或者项目投入预期规划不是特别大的公司或 ...

  7. 【ASP.NET 进阶】PDF文件在线预览(类似百度文库)

    工作需要完成文档的在线预览,现在完成了第一步PDF文件的预览,步骤是通过PDF转换工具pdf2swf.exe把PDF文件转换为SWF文件,然后通过FlexPaper就可以预览了.效果如下(GIF图片太 ...

  8. web office apps 在线预览实践

    摘要 在一些项目中需要在线预览office文档,包括word,excel,ppt等.达到预览文档的目的有很多方法,可以看我之前总结,在线预览的n种方案: [Asp.net]常见word,excel,p ...

  9. office web apps 整合到自己项目中(wopi实现在线预览编辑)

    借助office web apps实现在线预览和在线编辑 我所有的代码都是用go语言编写,你可以直接编译后使用,不用再有其他的操作. 最近项目实在太忙,这几天才有时间,这次是重头戏,要好好琢磨一下怎么 ...

随机推荐

  1. TCL基本语法

    所有的Tcl文件都以.tcl为扩展名. #!/usr/bin/tclsh puts "Hello, World!" TCL,我们使用新的行或分号终止代码前行.但分号不是必要的,如果 ...

  2. 管理Linux服务器的用户和组(续篇)

    用户切换 新建用户 useradd命令的选项 设置用户口令 passwd命令的选项 chage命令 修改用户帐户 禁用和恢复用户帐户 禁用和恢复用户帐户- Passwd命令 禁用和恢复用户帐户-直接修 ...

  3. crs_register/crs_unregister 注册与移除RAC服务 --zhuanzai

    crs_register命令主要是将资源注册到CRS.该方法通常结合crs_stat -p 或者crs_profile先创建配置文件.同时crs_register也具有更新CRS的功能.本文将描述cr ...

  4. Python2操作中文名文件乱码解决方案

    Python2默认是不支持中文的,一般我们在程序的开头加上#-*-coding:utf-8-*-来解决这个问题,但是在我用open()方法打开文件时,中文名字却显示成了乱码. 我先给大家说说Pytho ...

  5. 温故而知新-面向对象的PHP

    1 类的多态 不同的类对同一操作可以有不同的行为. 比如自行车和汽车都有移动这个成员函数行为, 那么自行车类可以移动,行为和汽车的移动行为肯定不同. 2 析构函数不能有参数 3 __set和__get ...

  6. 学python着几个要搞清楚WSGI和uWSGI区别

    1 WSGI是一种通信协议 2 uwsgi是一种线路协议而不是通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信. 3 而uWSGI是实现了uwsgi和WSGI两种协议的Web服务器.

  7. Git 知识总结

    版本控制git之一 - 仓库管理 版本控制git之二-分支 git https://mp.weixin.qq.com/s/96FS12DTzbjAJQ1ynRNqdg git init 初始化目录 g ...

  8. 自动把\r\n 替换成<p></p>

    function nl2p($string, $line_breaks = true, $xml = true) { // Remove existing HTML formatting to avo ...

  9. [ShaderStaff] Vignette Effect

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:GLSL | C 最近在看Cardboard实现,其中关于畸变的着色器代码中有加入 晕影Vignette 效果的实现,固在 ...

  10. SpringBoot31 重识Spring01-环境搭建、Actuator监控、属性配置、多环境配置

    1 前言 1.1 学习阶段说明 从2016年9月开始接触IT,学习经历主要分为以下三个阶段 1.1.1 入门阶段 从最基础的前端技术HTML.JavaScript.CSS开始入门,再到后端技术Java ...