首页 > 移动平台 > 详细

自定义开发的系统整合 office Web apps

时间:2017-01-19 07:57:44      阅读:306      评论:0      收藏:0      [点我收藏+]
Office文档的web应用叫WOPI Host或者WOPI Server。
把查看编辑操作Office文档的web应用叫WOPI Client或者叫WOPI applications。
所以,Office Web Apps充当的就是WOPI Client的角色。
SharePoint,Exchange,自己开发的文档管理系统充当的就是WOPI Host的角色。
再看一下,浏览器,server,client三者的请求顺序及关系

技术分享

其实网上有关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的实现:

  1. [Route("files/{name}/")]  
  2.        public CheckFileInfo GetFileInfo(string name, string access_token)  
  3.        {  
  4.            Validate(name, access_token);  
  5.            var fileInfo = _fileHelper.GetFileInfo(name);  
  6.            bool updateEnabled = false;  
  7.            if (bool.TryParse(WebConfigurationManager.AppSettings["updateEnabled"].ToString(), out updateEnabled))  
  8.            {  
  9.                fileInfo.SupportsUpdate = updateEnabled;  
  10.                fileInfo.UserCanWrite = updateEnabled;  
  11.                fileInfo.SupportsLocks = updateEnabled;  
  12.            }  
  13.            return fileInfo;  
  14.        }  
技术分享
这里的 Validate(name, access_token) 方法主要是验证请求的文件名name与参数access_token是否一致,主要是验证是否是非法访问,返回一个CheckFileInfo对象,CheckFileInfo的定义如下:

  1. public class CheckFileInfo  
  2.     {  
  3.         public CheckFileInfo()  
  4.         {  
  5.             this.SupportsUpdate = false;  
  6.             this.UserCanWrite = false;  
  7.         }  
  8.         public string BaseFileName { getset; }  
  9.         public string OwnerId { getset; }  
  10.         public long Size { getset; } //in bytes  
  11.         public string SHA256 { getset; } //SHA256: A 256 bit SHA-2-encoded [FIPS180-2] hash of the file contents  
  12.         public string Version { getset; }  //changes when file changes.  
  13.         public bool SupportsUpdate { getset; }  
  14.         public bool UserCanWrite { getset; }  
  15.         public bool SupportsLocks { getset; }  
  16.     }  
技术分享

现在在来看看第二个api的实现,主要返回对应文件的数据流:

  1. [Route("files/{name}/contents")]  
  2.         public HttpResponseMessage Get(string name, string access_token)  
  3.         {  
  4.             try  
  5.             {  
  6.                 Validate(name, access_token);  
  7.                 var file = HostingEnvironment.MapPath("~/App_Data/" + name);  
  8.                 var responseMessage = new HttpResponseMessage(HttpStatusCode.OK);  
  9.                 var stream = new FileStream(file, FileMode.Open, FileAccess.Read);  
  10.                 responseMessage.Content = new StreamContent(stream);  
  11.                 responseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");  
  12.                 return responseMessage;  
  13.             }  
  14.             catch (Exception ex)  
  15.             {  
  16.                 var errorResponseMessage = new HttpResponseMessage(HttpStatusCode.InternalServerError);  
  17.                 var stream = new MemoryStream(UTF8Encoding.Default.GetBytes(ex.Message ?? ""));  
  18.                 errorResponseMessage.Content = new StreamContent(stream);  
  19.                 return errorResponseMessage;  
  20.             }  
  21.         }  
技术分享

而第三个api是将返回的数据流保存到物理文件:

  1. [Route("files/{name}/contents")]  
  2.         public async void Post(string name, [FromUri] string access_token)  
  3.         {  
  4.             var body = await Request.Content.ReadAsByteArrayAsync();  
  5.             var appData = HostingEnvironment.MapPath("~/App_Data/");  
  6.             var fileExt = name.Substring(name.LastIndexOf(‘.‘) + 1);  
  7.             var outFile = Path.Combine(appData,name);  
  8.             File.WriteAllBytes(outFile, body);  
  9.         }  
技术分享

现在我们再来看看如何请求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的代码如下:

  1. public class LinkController : ApiController  
  2.     {  
  3.         /// <summary>  
  4.         /// Provides a link that can be used to Open a document in the relative viewer  
  5.         /// from the Office Web Apps server  
  6.         /// </summary>  
  7.         /// <param name="fileRequest">indicates the request type</param>  
  8.         /// <returns>A link usable for HREF</returns>  
  9.         public Link GetLink([FromUri] FileRequest fileRequest)  
  10.         {  
  11.             if (ModelState.IsValid)  
  12.             {  
  13.                 var xml = WebConfigurationManager.AppSettings["appDiscoveryXml"];  
  14.                 var wopiServer = WebConfigurationManager.AppSettings["appWopiServer"];  
  15.                 bool updateEnabled = false;  
  16.                 bool.TryParse(WebConfigurationManager.AppSettings["updateEnabled"], out updateEnabled);  
  17.                 WopiAppHelper wopiHelper = new WopiAppHelper(HostingEnvironment.MapPath(xml), updateEnabled);  
  18.   
  19.                 var result = wopiHelper.GetDocumentLink(wopiServer + fileRequest.name);  
  20.   
  21.                 var rv = new Link  
  22.                 {  
  23.                     Url = result  
  24.                 };  
  25.                 return rv;  
  26.             }  
  27.   
  28.             throw new ApplicationException("Invalid ModelState");  
  29.         }  
  30.     }  
  31.   
  32. public class WopiAppHelper  
  33.     {  
  34.         string _discoveryFile;  
  35.         bool _updateEnabled = false;  
  36.         WopiHost.wopidiscovery _wopiDiscovery;  
  37.   
  38.         public WopiAppHelper(string discoveryXml)  
  39.         {  
  40.             _discoveryFile = discoveryXml;  
  41.   
  42.             using (StreamReader file = new StreamReader(discoveryXml))  
  43.             {  
  44.                 XmlSerializer reader = new XmlSerializer(typeof(WopiHost.wopidiscovery));  
  45.                 var wopiDiscovery = reader.Deserialize(file) as WopiHost.wopidiscovery;  
  46.                 _wopiDiscovery = wopiDiscovery;  
  47.             }  
  48.         }  
  49.   
  50.         public WopiAppHelper(string discoveryXml, bool updateEnabled)  
  51.             : this(discoveryXml)  
  52.         {  
  53.             _updateEnabled = updateEnabled;  
  54.         }  
  55.   
  56.         public WopiHost.wopidiscoveryNetzoneApp GetZone(string AppName)  
  57.         {  
  58.             var rv = _wopiDiscovery.netzone.app.Where(c => c.name == AppName).FirstOrDefault();  
  59.             return rv;  
  60.         }  
  61.   
  62.         public string GetDocumentLink(string wopiHostandFile)  
  63.         {  
  64.             var fileName = wopiHostandFile.Substring(wopiHostandFile.LastIndexOf(‘/‘) + 1);  
  65.             var accessToken = GetToken(fileName);  
  66.             var fileExt = fileName.Substring(fileName.LastIndexOf(‘.‘) + 1);  
  67.             var netzoneApp = _wopiDiscovery.netzone.app.AsEnumerable()  
  68.                 .Where(c => c.action.Where(d => d.ext == fileExt).Count() > 0);  
  69.   
  70.             var appName = netzoneApp.FirstOrDefault();  
  71.   
  72.             if (null == appName) throw new ArgumentException("invalid file extension " + fileExt);  
  73.   
  74.             var rv = GetDocumentLink(appName.name, fileExt, wopiHostandFile, accessToken);  
  75.   
  76.             return rv;  
  77.         }  
  78.   
  79.         string GetToken(string fileName)  
  80.         {  
  81.             KeyGen keyGen = new KeyGen();  
  82.             var rv = keyGen.GetHash(fileName);  
  83.   
  84.             return HttpUtility.UrlEncode(rv);  
  85.         }  
  86.   
  87.         const string s_WopiHostFormat = "{0}?WOPISrc={1}&access_token={2}";  
  88.         //HACK:  
  89.         const string s_WopiHostFormatPdf = "{0}?PdfMode=1&WOPISrc={1}&access_token={2}";  
  90.   
  91.         public string GetDocumentLink(string appName, string fileExtension, string wopiHostAndFile, string accessToken)  
  92.         {  
  93.             var wopiHostUrlsafe = HttpUtility.UrlEncode(wopiHostAndFile.Replace(" ""%20"));  
  94.             var appStuff = _wopiDiscovery.netzone.app.Where(c => c.name == appName).FirstOrDefault();  
  95.   
  96.             if (null == appStuff)  
  97.                 throw new ApplicationException("Can‘t locate App: " + appName);  
  98.   
  99.             var action = _updateEnabled ? "edit" : "view";  
  100.             if (appName.Equals("WordPdf"))  
  101.             {  
  102.                 action = "view";  
  103.             }  
  104.             if (HttpContext.Current.Request.Browser.IsMobileDevice)  
  105.             {  
  106.                 action = "mobileView";  
  107.             }  
  108.             var appAction = appStuff.action.Where(c => c.ext == fileExtension && c.name == action).FirstOrDefault();  
  109.   
  110.             if (null == appAction)  
  111.                 throw new ApplicationException("Can‘t locate UrlSrc for : " + appName);  
  112.   
  113.             var endPoint = appAction.urlsrc.IndexOf(‘?‘);  
  114.             var endAction = appAction.urlsrc.Substring(0, endPoint);  
  115.   
  116.             string fullPath = null;  
  117.             ////HACK: for PDF now just append WordPdf option...  
  118.             if (fileExtension.Contains("pdf"))  
  119.             {  
  120.                 fullPath = string.Format( s_WopiHostFormatPdf, endAction, wopiHostUrlsafe, accessToken);  
  121.             }  
  122.             else  
  123.             {  
  124.                 fullPath = string.Format(s_WopiHostFormat, endAction,  wopiHostUrlsafe, accessToken);  
  125.             }  
  126.   
  127.             return fullPath;  
  128.         }  
  129.     }  
技术分享

相应的配置如下:

技术分享

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



自定义开发的系统整合 office Web apps

原文:http://blog.csdn.net/duanchuanttao/article/details/54602948

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!