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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...