Asp.net與office web apps的整合


其實網上有關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;
        }
    }
View Code

相應的配置如下:

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


免責聲明!

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



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