ASP.NET Core 2.0集成Office Online Server(OWAS)實現辦公文檔的線上預覽與編輯(支持word\excel\ppt\pdf等格式)

来源:https://www.cnblogs.com/Andre/archive/2018/08/28/9549874.html
-Advertisement-
Play Games

Office Online Server是微軟開發的一套基於Office實現線上文檔預覽編輯的技術框架(支持當前主流的瀏覽器,且瀏覽器上無需安裝任何插件,支持word、excel、ppt、pdf等文檔格式),其客戶端通過WebApi方式可集成到自已的應用中,支持Java、C#等語言。Office O... ...


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服務端的所有請求,並對不同的請求類型做出相應的處理。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在項目中經常使用連接資料庫的配置,如下所示 每個屬性都是硬編碼,有時候我們希望避免硬編碼,而是想讓這些值在運行時候再確定。Spring提供了兩種在運行時求值的方式:屬性占位符和Spring表達式語言 1、屬性占位符:使用${...}包裝屬性名稱 a、引入properties文件 test.prope ...
  • c/c++ 模板與STL小例子系列 模板類與友元函數 比如某個類是個模板類D,有個需求是需要重載D的operator實現這樣的友元需要3個必要步驟 1,在模板類D的實現代碼的上面聲明友元函數 2,在模板類D的實現代碼裡面聲明它是我的友元 3,實現友元函數 c++ include using name ...
  • 日誌? 日誌,就是用來記錄程式運行的時候都發生了什麼事。 事件按嚴重程度劃分level 事件內容: 時間 位置 事件的嚴重程度 level 內容 用 模塊實現 logging 模塊 日誌級別(level):DEBUG Logger.setLevel() ——設置日誌器將會處理的日誌消息的最低嚴重級別 ...
  • 軟體環境:Goland 倉庫地址 一、目的 之前用純邏輯壘完了一個可登入登出的線上多人聊天室(代碼倉庫地址),這次學習了Protobuf協議,於是想試著更新下聊天室的版本。 主要目的是為了掌握Protobuf的使用。 二、設計思路 通過Protobuf中內置好的編碼函數,將要發送的數據進行編碼,之後 ...
  • python的網路編程有不少難點,也容易忘記,最近我會陸續發出系統、完整pythonnet知識的博客,一邊複習一邊分享,感興趣的可以關註我。 話不多說,開始吧。 網路編程 目的:數據的傳輸 ISO(國際標準化組織) OSI七層模型 >網路通信的標準化流程 應用層:提供用戶服務,具體的內容由特定的程式 ...
  • #include <stdio.h> void highPrecision (int N ); int a[50000] = {0, 1}, length = 1; //開闢一個大的數組,全局變數length記錄長度 int main() { int N; while( ~scanf("%d", & ...
  • PID對象是代表Actor對象的進程,是能過Actor.Spawn(props)獲取的;它有什麼成員呢?既然代理Actor,首先有一個ID,標識自己是誰,Actor在Spawn時可以命名這個ID,否則會自動生成。還有三種向郵箱發消息的方法,Tell(),Request(),RequestAsync(... ...
  • 這是我第一次主導項目,沒有什麼經驗。本來項目的開發周期為十天,由於沒有什麼經驗,導致開發時間由十天變為了二十一天, 一直到今天才算是正式結束,明天交付給客戶。回想起這幾天的經過,想總總結一下。 1、在項目剛開始的時候,沒有對項目的整體有一個概念性的認識,雖然是看了需求文檔,但是需求文檔上寫的很模糊。 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...