Asp.net与office web apps的整合

Posted cynchanpin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
        }
    }

对应的配置例如以下:

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的整合的主要内容,如果未能解决你的问题,请参考以下文章

优雅地停止ASP.NET Core Web App(从Visual Studio调试器中),这是一个与IHostedService相关的问题

事后才在 ASP.NET Web App 中实现安全性

如何将 MVC5 RedirectResult () 重定向到 Asp.net 中的整页?

交换 Azure Web App 部署槽会注销 ASP.NET Core RC2 中的所有用户

ASP.NET Core中app.UseRouting()和app.UseEndpoints()区别

Azure Web App 中的 ASP.NET 5 环境名称