[转]Office文档在线编辑的实现之二

From:http://www.cnblogs.com/jianyi0115/archive/2007/07/15/818566.html

本篇将讲解如何实现客户端的office直接编辑数据库中的二进制形式保存的office文件。

实现的关键:模拟IIS,自己实现一个webdav的服务器端。

首先,我们简单了解一下webdav:
webdav,中文可以翻译为网络分布式协作协议,它解决了http协议中一个问题:http无法实现版本和单访问控制。
什么是单访问控制呢?假设我们有一个页面编辑某条数据,这个页面可以同时被多个用户使用,那么最终的数据是最后一个用户提交的数据,
而其他用户是不知道的.我们的99%的web程序都存在此问题,当然通过编码可以解决,但http协议本身并没有提供对这种情形的支持。

webdav协议在标准的http协议的基础上,扩展了以下请求动作(verb):
PUT:用于客户端推送二进制文件。(好像http有这个verb)
LOCK:用户锁定一个资源,保证资源的单访问
UNLOCK:解锁一个资源
OPTIONS:获取服务器可以支持的请求类型
DELETE:删除服务器文件
PROPFIND:查询文件属性
其他动作: OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH
要详细地了解webdav,大家可以google一下,或访问http://en.wikipedia.org/wiki/WebDAV

笔者在实现这个解决方案的时候,是采用fiddler,debug IE的http请求,才搞懂了IIS本身的实现机制,为了形象化,可以看一下webdav请求相应的
数据:
发起一个OPTIONS请求
OPTIONS /PMDemo/Test/待办事务.doc HTTP/1.1
User-Agent: Fiddler
Host: localhost

响应如下:
HTTP/1.1 200 OK
Date: Wed, 27 Dec 2006 11:34:03 GMT
Server: Microsoft-IIS/6.0
MicrosoftOfficeWebServer: 5.0_Pub
X-Powered-By: ASP.NET
MS-Author-Via: DAV
Content-Length: 0
Accept-Ranges: bytes
DASL: <DAV:sql>
DAV: 1, 2
Public: OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH
Allow: OPTIONS, TRACE, GET, HEAD, DELETE, PUT, COPY, MOVE, PROPFIND, PROPPATCH, SEARCH, LOCK, UNLOCK
Cache-Control: private

搞清楚了这些,下面我们的任务就是如何在asp.net中实现一个wevdav服务器.
显然,这要求我们需要在底层截获http请求,幸运的是asp.net中支持这种技术:HttpHandler.它可以让我们自己的代码来处理http请求.

首先,我们在web.config中做如下配置:

    <httpHandlers>
            <remove verb="*" path="*"/>            
            <add verb="GET,PUT,UNLOCK,LOCK,OPTIONS" path="*.doc,*.xml" type="Webdav.WebdavProtocolHandler,Webdav"/>
     </httpHandlers>

通过这个配置,使我们的WebdavProtocolHandler可以来处理webdav请求.

WebdavProtocolHandler类是一个标准的httphandler,实现了IHttpHandler接口,它按照客户端的请求类型,返回符合wevdav协议的数据.

WebdavProtocolHandler类需要按照不同的webdav请求动作,做不同的处理,那么怎么来实现这个类呢?
这里就要用到一个设计模式:命令模式.

首先定义一个接口:

public interface IVerbHandler
{
      void Process( HttpContext context );
}

实现对Options请求的处理:

class OptionsHandler : IVerbHandler
    {
        #region IVerbHandler 成员

        public void Process(System.Web.HttpContext context)
        {
            context.Response.AppendHeader("DASL", "<DAV:sql>");
            context.Response.AppendHeader("DAV", "1, 2");

            context.Response.AppendHeader("Public", "OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH");

            context.Response.AppendHeader("Allow", "OPTIONS, TRACE, GET, HEAD, DELETE, PUT, COPY, MOVE, PROPFIND, PROPPATCH, SEARCH, LOCK, UNLOCK");
        }

        #endregion
    }

webdav的请求verb多达15个以上,大多数情况下,我们并不需要一个完整的webdav支持,故我们只要对其中的几个进行实现即可。

实现对LOCK的支持:

 class LockHandler : IVerbHandler
    {
        #region IVerbHandler 成员

        public void Process(System.Web.HttpContext context)
        {
            context.Response.ContentType = "text/xml";

            string token = Guid.NewGuid().ToString() + ":" + DateTime.Now.Ticks.ToString() ; 

            context.Response.AppendHeader("Lock-Token", "<opaquelocktoken:" + token + ">");
          
            string xml = @"<?xml version=""1.0""?>
<a:prop xmlns:a=""DAV:""><a:lockdiscovery>
<a:activelock><a:locktype><a:write/></a:locktype>
<a:lockscope><a:exclusive/></a:lockscope><owner xmlns=""DAV:"">Administrator</owner><a:locktoken>
<a:href>opaquelocktoken:{0}</a:href></a:locktoken>
<a:depth>0</a:depth><a:timeout>Second-180</a:timeout></a:activelock></a:lockdiscovery>
</a:prop>";

            context.Response.Write( String.Format( xml , token ) );
            context.Response.End();
        }

        #endregion
    }

注意这篇文章的主题:实现在线编辑。并没有版本控制等其他内容,大家仔细看以上的代码,服务器并没有真正实现"锁定",只是假装告诉客户端,你要的资源已经给你锁定了,你可以放心的编辑了。当然,有兴趣的朋友可以实现真正的锁定,无非可以通过给数据做一个状态字段来实现。但注意,要考虑一些复杂的情况,如自动解锁(用户打开一个文档,然后关机了,文档岂不永远锁定了?)等等。

接着,我们实现UNLOCK,同样是假的:

class UnLockHandler : IVerbHandler
    {
        #region IVerbHandler 成员

        public void Process(System.Web.HttpContext context)
        {            
        }

        #endregion
    }

下面,我们将实现两个最重要的请求动作的处理:Get和Put, office请求打开一个服务器上的文件时,采用get请求,office保存一个文件到服务器上时,发送put请求。

首先,我们要考虑一种数据项标识的传递策略,即:客户端发起访问数据库的office文件行,那么如何确认数据行的主键?
有两种策略:
1)通过不同的文件名 , 如,请求http://localhost/weboffice/1.doc  这个请求主键 为1的文件。
2)通过文件路径, 如,请求http://localhost/weboffice/1/文件名.doc  这个请求主键为1的文件。
我们将采用策略2。

再返回到我们对web.config做的配置:

<add verb="GET,PUT,UNLOCK,LOCK,OPTIONS" path="*.doc,*.xml" type="Webdav.WebdavProtocolHandler,Webdav"/>

这个配置允许WebdavProtocolHandler处理所有对doc和xml的请求处理,为什么要允许xml呢,因为office2003之后,支持xml格式,可以直接在
数据库重以xml的格式存放office文件。

接着,我们要确认我们的数据存储结构,即,office文件在数据库中时如何存放的。

我们有一个附件表:Document

CREATE TABLE [dbo].[Document] (
    [DocumentId] [int] IDENTITY (1, 1) NOT NULL ,
    [Name] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
    [Description] [text] COLLATE Chinese_PRC_CI_AS NULL ,
    [CreateTime] [datetime] NULL ,
    [Size] [int] NULL ,
    [CreatorId] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
    [CreatorName] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
    [CreateYear] [int] NULL ,
    [ContentType] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
    [DeptId] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
    [DeptName] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
    [Content] [image] NULL ,
    [ModifyTime] [datetime] NULL ,
    [OwnerType] [varchar] (50) COLLATE Chinese_PRC_CI_AS NULL ,
    [TemplateAble] [bit] NULL 
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

设计一个文裆实体:

    [Serializable]
   public class Document
   {
       public Document()
       { }

       static public Document FromPostFile(System.Web.HttpPostedFile file , User user )
       {
           Document doc = new Document(file);
           doc.CreateTime = DateTime.Now;
           doc.CreatorId = user.Id;
           doc.CreatorName = user.Name; 
           doc.DeptId = user.OrgId;
           doc.DeptName = user.OrgName;
           return doc;
       }

       public Document(System.Web.HttpPostedFile file)
        {
            string[] strs = file.FileName.Split( '\\' );
            this.Name = strs[strs.Length - 1];
            Size = file.ContentLength;
            //读取文件的数据
            this.Content = new byte[Size];
            Stream fileDataStream = file.InputStream;
            fileDataStream.Read( this.Content , 0, Size );
            ContentType = file.ContentType;
        }

      private int _DocumentId;
      /// <summary>
      ///   任务名
      /// </summary>
      private string _Name;
      /// <summary>
      ///   任务描述
      /// </summary>
      private string _Description;
      /// <summary>
      ///   报表创建时间
      /// </summary>
      private DateTime _CreateTime = DateTime.Now ;
      private int _Size = 0 ;
       private byte[] _Data;
      /// <summary>
      ///   创建人Id
      /// </summary>
      private string _CreatorId;
      /// <summary>
      ///   创建人名
      /// </summary>
      private string _CreatorName;

      private int _CreateYear;
      private string _ContentType;
      /// <summary>
      ///   部门ID(便于统计)
      /// </summary>
      private string _DeptId;
      /// <summary>
      ///   部门名
      /// </summary>
      private string _DeptName;   
      // Property DocumentId
      public int DocumentId   
      {
         get
         {
            return _DocumentId;
         }
         set
         {
            this._DocumentId = value;
         }
      }      
      // Property Name
      public string Name   
      {
         get
         {
            return _Name;
         }
         set
         {
             this._Name = value;
         }
      }      
      // Property Description
      public string Description   
      {
         get
         {
            return _Description;
         }
         set
         {
             this._Description = value;
         }
      }      
      // Property CreateTime
      public DateTime CreateTime   
      {
         get
         {
            return _CreateTime;
         }
         set
         {
            this._CreateTime = value;
         }
      }
       private DateTime _ModifyTime = DateTime.Now;
       public DateTime ModifyTime
       {
           get
           {
               return _ModifyTime;
           }
           set
           {
               this._ModifyTime = value;
           }
       }      
      // Property Size
      public int Size   
      {
         get
         {
            return _Size;
         }
         set
         {
            this._Size = value;
         }
      }      
      // Property Data
      public byte[] Content   
      {
         get
         {
            return _Data;
         }
         set
         {
            this._Data = value;
         }
      }      
      // Property CreatorId
      public string CreatorId   
      {
         get
         {
            return _CreatorId;
         }
         set
         {
            this._CreatorId = value;
         }
      }

      // Property CreatorName
      public string CreatorName
      {
          get
          {
              return _CreatorName;
          }
          set
          {
              this._CreatorName = value;
          }
      }      
      // Property CreateYear
      public int CreateYear   
      {
         get
         {
            return _CreateYear;
         }
         set
         {
            this._CreateYear = value;
         }
      }      
      // Property ContentType
      //application/msword
      //text/plain
      public string ContentType   
      {
         get
         {
            return _ContentType;
         }
         set
         {
             this._ContentType = value;
         }
      }

      // Property DeptId
      public string DeptId
      {
          get
          {
              return _DeptId;
          }
          set
          {
              if (this._DeptId != value)
                  this._DeptId = value;
          }
      }
      // Property DeptName
      public string DeptName
      {
          get
          {
              return _DeptName;
          }
          set
          {
              this._DeptName = value;
          }
      }

       private string _Type;
       public string OwnerType
       {
           get
           {
               return _Type;
           }
           set
           {
               this._Type = value;
           }
       }
       private bool _TemplateAble;
      /// <summary>
      /// 是否可以作为模版
      /// </summary>
       public bool Templateable
       {
           get
           {
               return _TemplateAble;
           }
           set
           {
               this._TemplateAble = value;
           }
       }
       public override string ToString()
       {
           return Encoding.UTF8.GetString(this.Content);
       }        

       public static Document FromString(string s, User user)
       {
           Document doc = new Document();
           doc.CreateTime = DateTime.Now;
           doc.CreatorId = user.Id;
           doc.CreatorName = user.Name;
           doc.DeptId = user.OrgId;
           doc.DeptName = user.OrgName;
           doc.Content = Encoding.UTF8.GetBytes(s);
           doc.Size = doc.Content.Length;
           doc.ContentType = "text/plain";
           return doc;
       }
      public static string ByteToString( byte[] bytes )
      {
          return Encoding.UTF8.GetString( bytes );
      }
       public static byte[] StringToByte(string s)
       {
           return Encoding.UTF8.GetBytes(s); 
       }
       public string GetExtendName()
       {
           string[] arr = this.Name.Split( '.' );

           if (arr.Length < 1) return "";
           else return arr[ arr.Length - 1 ];
       }   
   }

考虑到数据操作逻辑的可变性,不同的项目里面附件表设计的不同,这里引入一个数据操作接口:

public interface IWebdavDocumentHandler
{
        Document GetDocument(int id);//获取文档数据
        void ModifyDocContent(int docId, byte[] data);//修改文档内容
}

具体的实现这里就不写了。

好了,我们的数据访问逻辑已经有了,那么首先看get动作处理的实现:

    class GetHandler : IVerbHandler
    {
        #region IVerbHandler 成员
        public void Process(System.Web.HttpContext context)
        {
            int id = WebdavProtocolHandler.GetDocumentId( context ); //获取到主键

            IWebdavDocumentHandler docSvr = new DefaultWebdavDocumentHandler(); //修改此处代码,实现不同的数据操作逻辑,可引入工厂模式
            Document doc = docSvr.GetDocument(id);

            if (doc == null)
            {
                context.Response.Write("文档不存在!");
                return;
            }

            context.Response.Clear();
            context.Response.ContentType = doc.ContentType;
            //下载文件名限制32字符 16 汉字
            int maxlength = 15;
            string fileName = doc.Name; //att.FileName ;
            if (fileName.Length > maxlength)
            {
                fileName = "-" + fileName.Substring(fileName.Length - maxlength, maxlength);
            }

            fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8); //必须编码,不然文件名会出现乱码
            context.Response.AppendHeader("Content-Disposition", "attachment;filename=" + fileName + "");          

            if (doc.Content != null && doc.Content.Length > 0)
                context.Response.BinaryWrite(doc.Content);

            context.Response.End();
        }
        #endregion
    }

很简单吧,跟我们普通实现文档下载的代码一样。

put动作的实现:

 class PutHandler : IVerbHandler
    {
        #region IVerbHandler 成员

        public void Process(System.Web.HttpContext context)
        {
            int docId = WebdavProtocolHandler.GetDocumentId(context);

            Document doc = GetDocFromInput(context.Request);

            doc.DocumentId = docId;

             IWebdavDocumentHandler docSvr = new DefaultWebdavDocumentHandler(); //修改此处代码,实现不同的数据操作逻辑,可引入工厂模式

            docSvr.ModifyDocContent( doc.DocumentId , doc.Content );
        }

        private Document GetDocFromInput(System.Web.HttpRequest request )
        {
            Document doc = new Document();
            //读取文件的数据
            doc.Content = new byte[ request.ContentLength ];
            doc.Size = request.ContentLength;
            Stream fileDataStream = request.InputStream;
            fileDataStream.Read( doc.Content , 0, doc.Size );
            doc.ContentType = request.ContentType;
            return doc;
        }
        #endregion
    }

OK,主要的动作都实现了,下面,我们需要WebdavProtocolHandler将各命令处理对象整合到一起:

    public class WebdavProtocolHandler : IHttpHandler
    {
        public static int GetDocumentId( HttpContext context )//按照前面确定的主键策略返回主键
        {
            string url = context.Request.Url.ToString();
            string[] arr = url.Split( '/' );
            string id = arr[arr.Length - 2];
            return Convert.ToInt32( id );
        }
        public void ProcessRequest(HttpContext context)
        {
            HttpRequest Request = context.Request;
            context.Response.AppendHeader("OpenWebDavServer", "1.0");
            string verb = Request.HttpMethod;
            //Log.Write(verb);
            IVerbHandler vh = GetVerbHandler( verb );

            if( vh == null )
                return ;

            vh.Process(context);      
        }

        private IVerbHandler GetVerbHandler(string verb)
        {
            switch (verb)
            {
                case "LOCK" :
                    return new LockHandler();
                case "UNLOCK":
                    return new UnLockHandler();
                case "GET":
                    return new GetHandler();
                case "PUT":
                    return new PutHandler();               
                case "OPTIONS":
                    return new OptionsHandler();
                default :
                    return null;
            }
        }    
        public bool IsReusable
        {
            get { return false; }
        }
    }

到这里呢,已经基本上算game over了,基于以上代码设计,可以完全实现office文档的在线编辑。若要通过链接直接打开编辑,可以
采用Office文档在线编辑的实现之一的Document_Edit2函数触发office编辑。

哦,IIS还需要做一点小配置:
1)将.doc , .xml 加入到站点虚拟目录的isapi映射, 不要选中 "确认文件是否存在",动作要选全部动作,
2)禁用IIS本身的Webdav扩展,
3)删除虚拟目录HTTP头中的自定义HTTP头: MicrosoftOfficeWebServer,如果有的话。

this is the real end.

时间: 2024-09-20 04:19:11

[转]Office文档在线编辑的实现之二的相关文章

Office文档在线编辑的一个实现方法_网页编辑器

Office xp之后的版本支持通过webdav协议(http的扩展)直接编辑服务器上的文件. IIS(6.0)支持webdav,这在IIS管理器的web服务扩展中可以看到.利用IIS作为webdav的服务器端,可以很容易的实现office(word,excel等)的在线编辑. 可以简单的实验一下: 确保IIS的webdav扩展安装并被启用了,建立一个虚拟目录test,在其中放一个word文档a.doc,然后打开word, 文件->打开->输入word文档的访问url(http://local

[转]Office文档在线编辑的实现之一

from : http://www.cnblogs.com/jianyi0115/archive/2007/03/16/677712.html 因为项目的关系,研究了一下Office的在线编辑功能,写出来共享一下. Office xp之后的版本支持通过webdav协议(http的扩展)直接编辑服务器上的文件. IIS(6.0)支持webdav,这在IIS管理器的web服务扩展中可以看到.利用IIS作为webdav的服务器端,可以很容易的实现office(word,excel等)的在线编辑. 可以

毕业设计 OFFICE文档在线考试系统 求大神帮助

问题描述 有没大神能代做一份毕业设计课题:OFFICE文档在线考试系统语言C#大致框架就是学生线下用WORD文档考试然后上传至网页通过老师上传的标准答案进行比对找对错价钱好商量详情联系QQ447446755 解决方案 解决方案二:紧急希望能帮助的大神速速与我联系解决方案三:你要的就是如何读取word,都不需要操作解决方案四:对呀,了解下如何读取word就行了解决方案五:如果你要代码的话我觉得你还不如去淘宝找,读word不知道怎么做的话看3楼的比较靠谱

Office文档在线预览

工具说明:通过传入文档的Web地址,即可进行Office文档的在线预览. 使用方式: 在http://office.qingshanboke.com地址后,通过url参数传入您想预览的文件路径. 如:/Default.aspx?url=http://office.qingshanboke.com/test.doc 完整地址: http://office.qingshanboke.com/Default.aspx?url=http://office.qingshanboke.com/test.do

利用XSL和ASP实现XML文档在线编辑

xml|在线 本文通过一个详细的例子,来阐述了在线编辑XML文档数据的方法.由于Netscape对XML的支持比较弱,因此,要实现跨平台的数据交换,数据的处理必须在服务器端进行.要编辑XML文档,首先要做的事情就是怎样把这些数据提取并显示给访问者,XSL为我们显示XML文件提供了一个很好的解决方案.下面的例子就是利用XSL样式单把XML文档显示出来,供用户进行编辑,然后再把编辑后的数据提交到服务器,在服务器端进行数据的更新.这里采用ASP(Active Server Pages)来完成我们的任务

cad文档在线编辑

问题描述 有什么办法可以实现到这样的文件管理系统:可以设置某个用户对某个文件的访问权限,用户通过客户端软件登陆服务器之后,可以在客户端打开某个文件并且在客户端的编辑窗口上根据用户权限对文件做相应的操作.这里有一点特别之处是:能浏览文件不一定就可以下载文件.而且文件是AUTOCAD文件. 解决方案 解决方案二:新出了款软件叫Qffice的文档系统,可以实现autocad在线编辑,还可以控制打开后的各种操作,比如,编辑.另存等操作解决方案三:在客户端安装各种插件,即可.

Java实现web在线预览office文档与pdf文档实例

1.首先我们需要找到可以把office转换成pdf的方法,查找资料发现有openoffice这一软件可以把office转换成pdf,这一软件先下载下来,然后记住自己安装的在那个位置.然后在cmd环境下进入安装目录的program目录,输入打开openoffice的命令:soffice -headless -accept="socket,host=127.0.0.1,port=8100;urp;" -nofirststartwizard 输入完成之后在任务管理器可以看见soffice.b

那些开源的php Office,快捷得在线编辑预览文档

在Office 15即将发布之际我又发现了那些在开源的PHPoffice,一般的程序员.设计师他们距离Office办公套件也是蛮遥远的,但是如果真的要用了总不可 能去下载一个吧即使是WPS也需要时间,所以不如在自己的服务器上建立一个建议的PHP Office! 那些开源的php Office,快捷得在线编辑预览文档 这里的替代品(既简单又复杂),可能更加适合你. 从此我们在线观看文档不再需要豆丁和百度文库了!   PHPExcel PHPExcel   几乎支持所有常用Excel的功能的PHPE

实现OFFICE文档的在线打开编辑,如何通过网页注册owssupp.dll

问题描述 网上找了很多资料,都是关于在线打开,编辑office文档的内容.千遍一律.现在的网络成了抄袭的天下.都试过了.不行.我说一下:客户端安装的是OFFICE2003版本(比较老的版本),其中就没有安装sharepoint组件,更别提在office11目录中有owssupp.dll文件了.然后我就从另外的机子上拷贝了owssupp.dll文件,并放到了office11目录.也运行了网上所说的注册命令:regsvr32......owssupp.dll.狗屁~~~出错阿.而且还是不能在线打开O