標籤:await html model user span first 物理 sso use
事實上網上有關office web app的整合已經有相關的文章了,典型的是怎樣整合Office Web Apps至自己開發的系統(一) 和怎樣整合Office Web Apps至自己開發的系統(二),微軟官網也有對應的demo。
這裡在簡單描寫敘述一下原理吧:office web apps(owas)扮演者一個客服端,它會訪問我們asp.net 網站的檔案然後呈現出來。而我們經常使用的API主要有例如以下3個:
GET api/wopi/files/{name}?access_token={access_token}
GET api/wopi/files/{name}/contents?
access_token={access_token}
POST api/wopi/files/{name}/contents?access_token={access_token}
至於每一個API做什麼 這裡就不多說。第一個是owas 檢查檔案,傳遞的資訊是json資料格式,第二個是owas擷取檔案流,第三個是owas post的檔案流(儲存改動檔案)。首先我們來看看第一個API的實現:
[Route("files/{name}/")] public CheckFileInfo GetFileInfo(string name, string access_token) { Validate(name, access_token); var fileInfo = _fileHelper.GetFileInfo(name); bool updateEnabled = false; if (bool.TryParse(WebConfigurationManager.AppSettings["updateEnabled"].ToString(), out updateEnabled)) { fileInfo.SupportsUpdate = updateEnabled; fileInfo.UserCanWrite = updateEnabled; fileInfo.SupportsLocks = updateEnabled; } return fileInfo; }這裡的 Validate(name, access_token) 方法主要是驗證請求的檔案名稱name與參數access_token是否一致。主要是驗證是否是非法訪問。返回一個CheckFileInfo對象。CheckFileInfo的定義例如以下:
public class CheckFileInfo { public CheckFileInfo() { this.SupportsUpdate = false; this.UserCanWrite = false; } public string BaseFileName { get; set; } public string OwnerId { get; set; } public long Size { get; set; } //in bytes public string SHA256 { get; set; } //SHA256: A 256 bit SHA-2-encoded [FIPS180-2] hash of the file contents public string Version { get; set; } //changes when file changes. public bool SupportsUpdate { get; set; } public bool UserCanWrite { get; set; } public bool SupportsLocks { get; set; } }
如今在來看看第二個api的實現。主要返回相應檔案的資料流:
[Route("files/{name}/contents")] public HttpResponseMessage Get(string name, string access_token) { try { Validate(name, access_token); var file = HostingEnvironment.MapPath("~/App_Data/" + name); var responseMessage = new HttpResponseMessage(HttpStatusCode.OK); var stream = new FileStream(file, FileMode.Open, FileAccess.Read); responseMessage.Content = new StreamContent(stream); responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); return responseMessage; } catch (Exception ex) { var errorResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError); var stream = new MemoryStream(UTF8Encoding.Default.GetBytes(ex.Message ?? "")); errorResponseMessage.Content = new StreamContent(stream); return errorResponseMessage; } }
而第三個api是將返回的資料流儲存到物理檔案:
[Route("files/{name}/contents")] public async void Post(string name, [FromUri] string access_token) { var body = await Request.Content.ReadAsByteArrayAsync(); var appData = HostingEnvironment.MapPath("~/App_Data/"); var fileExt = name.Substring(name.LastIndexOf(‘.‘) + 1); var outFile = Path.Combine(appData,name); File.WriteAllBytes(outFile, body); }
如今我們再來看看怎樣請求owas。也就是相應的url是怎麼產生的。
比如我的owas server是owas.contoso.com。那麼我們在配置好owas後就能夠訪問http://owas.contoso.com/hosting/discovery
這裡我們以excel為例 大家看到上面有view、edit、mobileview三個action。這裡的app是一個excel。我們知道我們物理檔案的尾碼找到相應的app。在依據我們系統的配置採用edit還是view action,假設是pdf 我們僅僅能採用相應的view。假設請求是mobile發起的話, 那麼我們僅僅能用mobileview。 找到相應的action後我們就擷取到相應的urlsrc屬性,這裡我們實際須要的url地址是 http://owas.contoso.com/x/_layouts/xlviewerinternal.aspx這個東東。那麼擷取這個url的代碼例如以下:
public class LinkController : ApiController { /// <summary> /// Provides a link that can be used to Open a document in the relative viewer /// from the Office Web Apps server /// </summary> /// <param name="fileRequest">indicates the request type</param> /// <returns>A link usable for HREF</returns> public Link GetLink([FromUri] FileRequest fileRequest) { if (ModelState.IsValid) { var xml = WebConfigurationManager.AppSettings["appDiscoveryXml"]; var wopiServer = WebConfigurationManager.AppSettings["appWopiServer"]; bool updateEnabled = false; bool.TryParse(WebConfigurationManager.AppSettings["updateEnabled"], out updateEnabled); WopiAppHelper wopiHelper = new WopiAppHelper(HostingEnvironment.MapPath(xml), updateEnabled); var result = wopiHelper.GetDocumentLink(wopiServer + fileRequest.name); var rv = new Link { Url = result }; return rv; } throw new ApplicationException("Invalid ModelState"); } }public class WopiAppHelper { string _discoveryFile; bool _updateEnabled = false; WopiHost.wopidiscovery _wopiDiscovery; public WopiAppHelper(string discoveryXml) { _discoveryFile = discoveryXml; using (StreamReader file = new StreamReader(discoveryXml)) { XmlSerializer reader = new XmlSerializer(typeof(WopiHost.wopidiscovery)); var wopiDiscovery = reader.Deserialize(file) as WopiHost.wopidiscovery; _wopiDiscovery = wopiDiscovery; } } public WopiAppHelper(string discoveryXml, bool updateEnabled) : this(discoveryXml) { _updateEnabled = updateEnabled; } public WopiHost.wopidiscoveryNetzoneApp GetZone(string AppName) { var rv = _wopiDiscovery.netzone.app.Where(c => c.name == AppName).FirstOrDefault(); return rv; } public string GetDocumentLink(string wopiHostandFile) { var fileName = wopiHostandFile.Substring(wopiHostandFile.LastIndexOf(‘/‘) + 1); var accessToken = GetToken(fileName); var fileExt = fileName.Substring(fileName.LastIndexOf(‘.‘) + 1); var netzoneApp = _wopiDiscovery.netzone.app.AsEnumerable() .Where(c => c.action.Where(d => d.ext == fileExt).Count() > 0); var appName = netzoneApp.FirstOrDefault(); if (null == appName) throw new ArgumentException("invalid file extension " + fileExt); var rv = GetDocumentLink(appName.name, fileExt, wopiHostandFile, accessToken); return rv; } string GetToken(string fileName) { KeyGen keyGen = new KeyGen(); var rv = keyGen.GetHash(fileName); return HttpUtility.UrlEncode(rv); } const string s_WopiHostFormat = "{0}?WOPISrc={1}&access_token={2}"; //HACK: const string s_WopiHostFormatPdf = "{0}?PdfMode=1&WOPISrc={1}&access_token={2}"; public string GetDocumentLink(string appName, string fileExtension, string wopiHostAndFile, string accessToken) { var wopiHostUrlsafe = HttpUtility.UrlEncode(wopiHostAndFile.Replace(" ", "%20")); var appStuff = _wopiDiscovery.netzone.app.Where(c => c.name == appName).FirstOrDefault(); if (null == appStuff) throw new ApplicationException("Can‘t locate App: " + appName); var action = _updateEnabled ? "edit" : "view"; if (appName.Equals("WordPdf")) { action = "view"; } if (HttpContext.Current.Request.Browser.IsMobileDevice) { action = "mobileView"; } var appAction = appStuff.action.Where(c => c.ext == fileExtension && c.name == action).FirstOrDefault(); if (null == appAction) throw new ApplicationException("Can‘t locate UrlSrc for : " + appName); var endPoint = appAction.urlsrc.IndexOf(‘?‘); var endAction = appAction.urlsrc.Substring(0, endPoint); string fullPath = null; ////HACK: for PDF now just append WordPdf option... if (fileExtension.Contains("pdf")) { fullPath = string.Format( s_WopiHostFormatPdf, endAction, wopiHostUrlsafe, accessToken); } else { fullPath = string.Format(s_WopiHostFormat, endAction, wopiHostUrlsafe, accessToken); } return fullPath; } }
對應的配置例如以下:
appDiscoveryXml 是我們owas(http://owas.contoso.com/hosting/discovery)產生的資料檔案。appWopiServer 表示我們的owas將要訪問interface地址。updateEnabled主要是表示owas能否夠改動我們的文檔,假設是true 我們上面的action 採用edit,為false採用view。
appHmacKey僅僅是資料加密的一個key。產生的url
注意這裡的配置是updateEnabled=true 表示owas是能夠編輯檔案的,
當我們點擊在瀏覽器編輯 結果
改動後能夠直接儲存:
點擊確認後就能夠直接儲存。 pptx的編輯模式例如以下:
這裡的docx檔案的編輯模式一直都在報錯搞了非常久也沒搞定。錯誤資訊例如以下,假設大家知道還請指導指導:
pdf是沒有編輯模式的,如今再來看看excel的僅僅讀模式(view)例如以下:
這裡的菜單中並不包括“在瀏覽器中編輯”。當中第15行是我剛才改動的新資料。
docx和pptx的僅僅讀模式就不貼圖了,在mobile的執行結果例如以下(我這裡是用android手機訪問我的網站,因為是通過wifi來訪問自己的電腦上的網站,這裡須要把電腦的全名改為IP地址)。
注意上面的url是192.168.1.25XXX,這裡的ip是owas.contoso.com的IP。這裡總結一下的測試結果例如以下:
| |
view |
edit |
mobileview |
remark |
| word |
通過 |
未通過 |
通過 |
在http和https協議下view都通過。edit view沒有通過。mobileview僅僅測試了http協議 |
| excel |
通過 |
通過 |
通過 |
在http和https協議下view和edit都通過,mobileview僅僅測試了http協議 |
| ppt |
通過 |
通過 |
通過 |
在http和https協議下view和edit都通過,mobileview僅僅測試了http協議 |
| pdf |
通過 |
不存在edit action |
未通過 |
view在http協議下通過,在https在協議下未通過,mobileview 未通過 |
這裡我把問題的重心放在word的edit上面,對於pdf 在owas採用https以及在mobile上不能訪問的原因未未做調查。知道這些問題的革命前輩還請指教。
原始碼:http://download.csdn.net/detail/dz45693/7215395
https://code.msdn.microsoft.com/office/Building-an-Office-Web-f98650d6
Asp.net與office web apps的整合