ASP.NET Core 2.0集成OWAS实现文档在线预览与编辑(Word、Excel、PPT、PDF)

2018 年 8 月 30 日 DotNet

(点击上方蓝字,可快速关注我们)


来源:Andre-Hub

cnblogs.com/Andre/p/9549874.html


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服务端的所有请求,并对不同的请求类型做出相应的处理。


看完本文有收获?请转发分享给更多人

关注「DotNet」,提升.Net技能 

登录查看更多
0

相关内容

【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
模型优化基础,Sayak Paul,67页ppt
专知会员服务
75+阅读 · 2020年6月8日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
【WWW2020-微软】理解用户行为用于文档推荐
专知会员服务
35+阅读 · 2020年4月5日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
225+阅读 · 2020年3月22日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
22+阅读 · 2019年11月7日
【干货】谷歌Joshua Gordon 《TensorFlow 2.0讲解》,63页PPT
专知会员服务
27+阅读 · 2019年11月2日
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
基于Web页面验证码机制漏洞的检测
FreeBuf
7+阅读 · 2019年3月15日
百度开源项目OpenRASP快速上手指南
黑客技术与网络安全
5+阅读 · 2019年2月12日
深度学习 | 免费使用Google Colab的GPU云计算平台
沈浩老师
12+阅读 · 2018年2月4日
浅谈浏览器 http 的缓存机制
前端大全
6+阅读 · 2018年1月21日
33款可用来抓数据的开源爬虫软件工具 (推荐收藏)
数据科学浅谈
7+阅读 · 2017年7月29日
Arxiv
4+阅读 · 2018年11月7日
Feature Selection Library (MATLAB Toolbox)
Arxiv
7+阅读 · 2018年8月6日
Bidirectional Attention for SQL Generation
Arxiv
4+阅读 · 2018年6月21日
VIP会员
相关VIP内容
【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
模型优化基础,Sayak Paul,67页ppt
专知会员服务
75+阅读 · 2020年6月8日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
【WWW2020-微软】理解用户行为用于文档推荐
专知会员服务
35+阅读 · 2020年4月5日
【干货书】流畅Python,766页pdf,中英文版
专知会员服务
225+阅读 · 2020年3月22日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
【电子书】C++ Primer Plus 第6版,附PDF
专知会员服务
87+阅读 · 2019年11月25日
【电子书】Flutter实战305页PDF免费下载
专知会员服务
22+阅读 · 2019年11月7日
【干货】谷歌Joshua Gordon 《TensorFlow 2.0讲解》,63页PPT
专知会员服务
27+阅读 · 2019年11月2日
相关资讯
支持多标签页的Windows终端:Fluent 终端
Python程序员
7+阅读 · 2019年4月15日
基于Web页面验证码机制漏洞的检测
FreeBuf
7+阅读 · 2019年3月15日
百度开源项目OpenRASP快速上手指南
黑客技术与网络安全
5+阅读 · 2019年2月12日
深度学习 | 免费使用Google Colab的GPU云计算平台
沈浩老师
12+阅读 · 2018年2月4日
浅谈浏览器 http 的缓存机制
前端大全
6+阅读 · 2018年1月21日
33款可用来抓数据的开源爬虫软件工具 (推荐收藏)
数据科学浅谈
7+阅读 · 2017年7月29日
Top
微信扫码咨询专知VIP会员