轉載自:https://www.cnblogs.com/Andre/p/9549874.htmlhtml
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的過程當中也遇到了很多的問題,因此就有了今天這篇文章。json
安裝Office Online Server瀏覽器
微軟的東西在安裝上都是很簡單的,下載安裝包一路」下一步「就可完成。也可參考以下說明來進行安裝:https://docs.microsoft.com/zh-cn/officeonlineserver/deploy-office-online-server緩存
完成安裝後會在服務器上的IIS上自動建立兩個網站,分別爲:HTTP80、HTTP809。其中HTTP80站綁定80、443端口,HTTP809站綁定80九、810端口。安全
業務關係服務器
一、Office Online Server服務端(WOPI Server),安裝在服務器上用於受理來自客戶端的預覽、編輯請求等。服務端很吃內存的,單機必定不能低於8G內存。併發
二、Office Online Server客戶端(WOPI Client),這裏由於集成在了自已的項目中,因此Office Online Server客戶端也就是自已的項目中的子系統。app
用戶經過項目中的業務系統請求客戶端併發起對某一文檔的預覽或編輯請求,客戶端接受請求後再經過調用服務端的WebApi完成一系列約定通信後,服務端在線輸出文檔並完成預覽與編輯功能。框架
實現原理async
可經過以下圖(圖片來自互聯網)能清晰的看出瀏覽器、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上的文檔預覽/編輯功能就完成了。夠簡單的吧!!
總結
一、Office Online Server服務端建議在服務器上獨立部署,不要與其它業務系統混合部署。由於這貨實在是太能吃內存了,其內部用了WebCached緩存機制是致使內存增高的一個因素。
二、Office Online Server不少資料上要求要用AD域,但我實際在集成客戶端時沒有涉及到這塊,也就是說服務端是開放的,但客戶端是經過自行頒發的Token與驗證來保障安全的。
三、利用編寫中間件攔截器,並在Startup.cs文件中注入中間件的方式來截獲來自WOPI服務端的全部請求,並對不一樣的請求