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


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

 

聲明

本文為作者原創,轉載請備注出處與保留原文地址,謝謝。如文章能給您帶來幫助,請點下推薦或關注,感謝您的支持!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM