码迷,mamicode.com
首页 > 移动开发 > 详细

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

时间:2017-01-19 07:59:53      阅读:435      评论:0      收藏:0      [点我收藏+]

标签:title   net   svg   link   control   知识库   自定义   over   using   

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

标签:title   net   svg   link   control   知识库   自定义   over   using   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!