• ASP.NET Core2集成Office Online Server(OWAS)实现办公文档的在线预览与编辑(支持wordexcelpptpdf等格式)


    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客户端的安全手段。

    19092925-56e50ede7a59467d8ba8d9047f5dfcb9

    实现代码

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

      1 using Microsoft.AspNetCore.Http;
      2 using Newtonsoft.Json;
      3 using System;
      4 using System.Collections.Generic;
      5 using System.IO;
      6 using System.Linq;
      7 using System.Text;
      8 using System.Threading;
      9 using System.Threading.Tasks;
     10 using System.Web;
     11 //编写一个处理WOPI请求的客户端拦截器
     12 namespace Lezhima.Wopi.Base
     13 {
     14     public class ContentProvider  
     15     {
     16         //声明请求代理
     17         private readonly RequestDelegate _nextDelegate;
     18 
     19 
     20         public ContentProvider(RequestDelegate nextDelegate)
     21         {
     22             _nextDelegate = nextDelegate;
     23         }
     24 
     25 
     26         //拉截并接受所有请求
     27         public async Task Invoke(HttpContext context)
     28         {
     29 		//判断是否为来自WOPI服务端的请求
     30             if (context.Request.Path.ToString().ToLower().IndexOf("files") >= 0)
     31             {
     32                 WopiRequest requestData = ParseRequest(context.Request);
     33 
     34                 switch (requestData.Type)
     35                 {
     36 			//获取文件信息
     37                     case RequestType.CheckFileInfo:
     38                         await HandleCheckFileInfoRequest(context, requestData);
     39                         break;
     40 
     41                     //尝试解锁并重新锁定
     42                     case RequestType.UnlockAndRelock:
     43                         HandleUnlockAndRelockRequest(context, requestData);
     44                         break;
     45 
     46                     //获取文件
     47                     case RequestType.GetFile:
     48                         await HandleGetFileRequest(context, requestData);
     49                         break;
     50 
     51                     //写入文件
     52                     case RequestType.PutFile:
     53                         await HandlePutFileRequest(context, requestData);
     54                         break;
     55 
     56                     default:
     57                         ReturnServerError(context.Response);
     58                         break;
     59                 }
     60             }
     61             else
     62             {
     63                 await _nextDelegate.Invoke(context);
     64             }
     65         }
     66 
     67 
     68 
     69 
     70         /// <summary>
     71         /// 接受并处理获取文件信息的请求
     72         /// </summary>
     73         /// <remarks>
     74         /// </remarks>
     75         private async Task HandleCheckFileInfoRequest(HttpContext context, WopiRequest requestData)
     76         {
     77 		//判断是否有合法token    
     78             if (!ValidateAccess(requestData, writeAccessRequired: false))
     79             {
     80                 ReturnInvalidToken(context.Response);
     81                 return;
     82             }
     83             //获取文件           
     84             IFileStorage storage = FileStorageFactory.CreateFileStorage();
     85             DateTime? lastModifiedTime = DateTime.Now;
     86             try
     87             {
     88                 CheckFileInfoResponse responseData = new CheckFileInfoResponse()
     89                 {
     90 			//获取文件名称
     91                     BaseFileName = Path.GetFileName(requestData.Id),
     92                     Size = Convert.ToInt32(size),
     93                     Version = Convert.ToDateTime((DateTime)lastModifiedTime).ToFileTimeUtc().ToString(),
     94                     SupportsLocks = true,
     95                     SupportsUpdate = true,
     96                     UserCanNotWriteRelative = true,
     97 
     98                     ReadOnly = false,
     99                     UserCanWrite = true
    100                 };
    101 
    102                 var jsonString = JsonConvert.SerializeObject(responseData);
    103 
    104                 ReturnSuccess(context.Response);
    105 
    106                 await context.Response.WriteAsync(jsonString);
    107 
    108             }
    109             catch (UnauthorizedAccessException ex)
    110             {
    111                 ReturnFileUnknown(context.Response);
    112             }
    113         }
    114 
    115         /// <summary>
    116         /// 接受并处理获取文件的请求
    117         /// </summary>
    118         /// <remarks>
    119         /// </remarks>
    120         private async Task HandleGetFileRequest(HttpContext context, WopiRequest requestData)
    121         {
    122      	//判断是否有合法token    
    123             if (!ValidateAccess(requestData, writeAccessRequired: false))
    124             {
    125                 ReturnInvalidToken(context.Response);
    126                 return;
    127             }
    128 
    129 
    130             //获取文件             
    131             var stream = await storage.GetFile(requestData.FileId);
    132 
    133             if (null == stream)
    134             {
    135                 ReturnFileUnknown(context.Response);
    136                 return;
    137             }
    138 
    139             try
    140             {
    141                 int i = 0;
    142                 List<byte> bytes = new List<byte>();
    143                 do
    144                 {
    145                     byte[] buffer = new byte[1024];
    146                     i = stream.Read(buffer, 0, 1024);
    147                     if (i > 0)
    148                     {
    149                         byte[] data = new byte[i];
    150                         Array.Copy(buffer, data, i);
    151                         bytes.AddRange(data);
    152                     }
    153                 }
    154                 while (i > 0);
    155 
    156 
    157                 ReturnSuccess(context.Response);
    158 		    await context.Response.Body.WriteAsync(bytes, bytes.Count);
    159 
    160             }
    161             catch (UnauthorizedAccessException)
    162             {
    163                 ReturnFileUnknown(context.Response);
    164             }
    165             catch (FileNotFoundException ex)
    166             {
    167                 ReturnFileUnknown(context.Response);
    168             }
    169 
    170         }
    171 
    172         /// <summary>
    173         /// 接受并处理写入文件的请求
    174         /// </summary>
    175         /// <remarks>
    176         /// </remarks>
    177         private async Task HandlePutFileRequest(HttpContext context, WopiRequest requestData)
    178         {
    179 		//判断是否有合法token    
    180             if (!ValidateAccess(requestData, writeAccessRequired: true))
    181             {
    182                 ReturnInvalidToken(context.Response);
    183                 return;
    184             }
    185 
    186             try
    187             {
    188                 //写入文件			
    189                 int result = await storage.UploadFile(requestData.FileId, context.Request.Body);
    190                 if (result != 0)
    191                 {
    192                     ReturnServerError(context.Response);
    193                     return;
    194                 }
    195 
    196                 ReturnSuccess(context.Response);
    197             }
    198             catch (UnauthorizedAccessException)
    199             {
    200                 ReturnFileUnknown(context.Response);
    201             }
    202             catch (IOException ex)
    203             {
    204                 ReturnServerError(context.Response);
    205             }
    206         }
    207 
    208 
    209 
    210         private static void ReturnServerError(HttpResponse response)
    211         {
    212             ReturnStatus(response, 500, "Server Error");
    213         }
    214 
    215     }
    216 }

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

      1         public void Configure(IApplicationBuilder app, IHostingEnvironment env)
      2         {
      3             if (env.IsDevelopment())
      4             {
      5                 app.UseDeveloperExceptionPage();
      6                 app.UseBrowserLink();
      7             }
      8             else
      9             {
     10                 app.UseExceptionHandler("/Home/Error");
     11             }
     12 
     13             app.UseStaticFiles();
     14             app.UseAuthentication();
     15 
     16 	        //注入中间件拦截器,这是将咱们写的那个Wopi客户端拦截器注入进来
     17             app.UseMiddleware<ContentProvider>();
     18 
     19             app.UseMvc(routes =>
     20             {
     21                 routes.MapRoute(
     22                     name: "default",
     23                     template: "{controller=Home}/{action=Index}/{name?}");
     24             });
     25         }

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

    总结

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

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

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

    声明

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

  • 相关阅读:
    [Java基础] 深入jar包:从jar包中读取资源文件
    [Git] git merge和rebase的区别
    windows和linux中换行符的转换
    使用 scikit-learn 实现多类别及多标签分类算法
    python 特征缺失值填充
    多输出回归问题
    python DataFrame获取行数、列数、索引及第几行第几列的值
    Xgboost 模型保存和载入()
    pandas所占内存释放
    SSE,MSE,RMSE,R-square 指标讲解
  • 原文地址:https://www.cnblogs.com/Miidy/p/9549874.html
Copyright © 2020-2023  润新知