上篇文章 http://www.cnblogs.com/jianyi0115/archive/2007/03/16/677712.html
讲述了如何通过iis的webdav支持实现客户端的office直接编辑服务器上的文件,
本篇将讲解如何实现客户端的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请求.< remove verb ="*" path ="*" />
< add verb ="GET,PUT,UNLOCK,LOCK,OPTIONS" path ="*.doc,*.xml" type ="Webdav.WebdavProtocolHandler, Webdav " />
</ httpHandlers >
WebdavProtocolHandler类是一个标准的httphandler,实现了IHttpHandler接口,它按照客户端的请求类型,返回符合wevdav协议的数据.
WebdavProtocolHandler类需要按照不同的webdav请求动作,做不同的处理,那么怎么来实现这个类呢?
这里就要用到一个设计模式:命令模式.
首先定义一个接口:
public
interface
IVerbHandler
{
void Process( HttpContext context );
}
{
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支持,故我们只要对其中的几个进行实现即可。
{
#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
}
实现对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
}
{
#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
}
{
#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
设计一个文裆实体:
[ 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 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);//修改文档内容
}
{
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
}
很简单吧,跟我们普通实现文档下载的代码一样。
{
#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
}
{
#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 ; }
}
}
{
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.
0
0
(请您对文章做出评价)
FeedBack:
2201821
@jianyi0115
没有HTTP头: MicrosoftOfficeWebServer
我的调用代码为:
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>无标题页</title>
<script language="javascript" type="text/javascript">
var __OpenDocuments = null ;
function documentEdit( url )
{
if( __OpenDocuments == null ) {
try{
__OpenDocuments = new ActiveXObject("SharePoint.OpenDocuments.3"); //for office 2007
}
catch(e){}
if( __OpenDocuments == null || typeof(__OpenDocuments) == "undefined" ) {
try{
__OpenDocuments = new ActiveXObject("SharePoint.OpenDocuments.2"); //for office 2003
}
catch(e){}
}
if( __OpenDocuments == null || typeof(__OpenDocuments) == "undefined" ) {
alert( "请安装Word(2003或更高版本)" );
return ;
}
}
var result = __OpenDocuments.EditDocument( url , "Word.Document" );
if( result == false ) {
alert( "无法打开文档." );
}
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<input type="button" value="打开文档" οnclick="documentEdit('http://localhost/Office/1/1.doc');return false;" />
</div>
</form>
</body>
</html>
回复 引用
没有HTTP头: MicrosoftOfficeWebServer
我的调用代码为:
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>无标题页</title>
<script language="javascript" type="text/javascript">
var __OpenDocuments = null ;
function documentEdit( url )
{
if( __OpenDocuments == null ) {
try{
__OpenDocuments = new ActiveXObject("SharePoint.OpenDocuments.3"); //for office 2007
}
catch(e){}
if( __OpenDocuments == null || typeof(__OpenDocuments) == "undefined" ) {
try{
__OpenDocuments = new ActiveXObject("SharePoint.OpenDocuments.2"); //for office 2003
}
catch(e){}
}
if( __OpenDocuments == null || typeof(__OpenDocuments) == "undefined" ) {
alert( "请安装Word(2003或更高版本)" );
return ;
}
}
var result = __OpenDocuments.EditDocument( url , "Word.Document" );
if( result == false ) {
alert( "无法打开文档." );
}
}
</script>
</head>
<body>
<form id="form1" runat="server">
<div>
<input type="button" value="打开文档" οnclick="documentEdit('http://localhost/Office/1/1.doc');return false;" />
</div>
</form>
</body>
</html>
回复 引用
@goo
Word客户端发送http请求服务器上一个文件:http://some/some/some.doc; 这里some.doc不是一个具体的文件,实际上对应一个httpHandler,some.doc返回doc流到word;
修改后保存,Word客户端会采用put方法,将所有文档内容发送到服务器端
,服务器端进行相应处理。
说白了,跟http基本是一回事。
回复 引用 查看
Word客户端发送http请求服务器上一个文件:http://some/some/some.doc; 这里some.doc不是一个具体的文件,实际上对应一个httpHandler,some.doc返回doc流到word;
修改后保存,Word客户端会采用put方法,将所有文档内容发送到服务器端
,服务器端进行相应处理。
说白了,跟http基本是一回事。
回复 引用 查看
博主你好,看了你得word在线编辑之实现,我在visual studio的webserver下调试,完全正常,但是在iis5下禁用webdav服务后,调试出错。用fiddler跟踪,发现
OPTIONS /project/ProjectDocument/Using/97b39ac0-310a-4c5d-8f2d-ef82d126a8c4/732/746/774/366fe193-4aa1-46a4-ac24-1843ace750ec.xls HTTP/1.1
Translate: f
User-Agent: Microsoft Data Access Internet Publishing Provider Protocol Discovery
Host: localhost
Content-Length: 0
Proxy-Connection: Keep-Alive
Pragma: no-cache
这个请求的返回response是一个html出错文本,主要错误信息:
HTTP 403.1 禁止访问:禁止可执行访问。您试图从目录中执行CGI、ISAPI 或其他可执行程序,但该目录不允许执行程序
从而导致无法在iis5上在线编辑。iis5所有权限都给了,并且在开启默认webdav的情况下,可以在线编辑。(当然,不走咱们的自定义httpmodle).
试了很久,还请博主指点。
msn: rui.star@126.com mail:vstarcn@gmail.com
回复 引用
OPTIONS /project/ProjectDocument/Using/97b39ac0-310a-4c5d-8f2d-ef82d126a8c4/732/746/774/366fe193-4aa1-46a4-ac24-1843ace750ec.xls HTTP/1.1
Translate: f
User-Agent: Microsoft Data Access Internet Publishing Provider Protocol Discovery
Host: localhost
Content-Length: 0
Proxy-Connection: Keep-Alive
Pragma: no-cache
这个请求的返回response是一个html出错文本,主要错误信息:
HTTP 403.1 禁止访问:禁止可执行访问。您试图从目录中执行CGI、ISAPI 或其他可执行程序,但该目录不允许执行程序
从而导致无法在iis5上在线编辑。iis5所有权限都给了,并且在开启默认webdav的情况下,可以在线编辑。(当然,不走咱们的自定义httpmodle).
试了很久,还请博主指点。
msn: rui.star@126.com mail:vstarcn@gmail.com
回复 引用
jianyi兄,我实现LOCK,但是返回的RESPONSE不能使WORD弹出只读编辑那个对话框.我跟默认的IIS的LOCK对比如下:
--我模拟的lock,这样word没有任何反应?--------------
HTTP/1.1 200 OK
Date: Fri, 18 Apr 2008 06:26:42 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
OpenWebDavServer: 1.0
HTTP/1.1: 423 Locked
Connection: close
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Length: 404
<?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:">guest</owner><a:locktoken>
<a:href>opaquelocktoken:eb56e920-3483-442a-a8b3-188c0394ab15:633441292022031250</a:href></a:locktoken>
<a:depth>0</a:depth><a:timeout>Second-180</a:timeout></a:activelock></a:lockdiscovery></a:prop>
---IIS自己的webdav的正确的Lock
HTTP/1.1 423 Locked
Connection: close
Date: Fri, 18 Apr 2008 06:35:12 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Content-Type: text/xml
Content-Length: 396
<?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:B23DB6F1-294B-424C-9EFD-A64DC4402030:214748364852</a:href></a:locktoken><a:depth>0</a:depth><a:timeout>Second-180</a:timeout></a:activelock></a:lockdiscovery></a:prop>
回复 引用
--我模拟的lock,这样word没有任何反应?--------------
HTTP/1.1 200 OK
Date: Fri, 18 Apr 2008 06:26:42 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
OpenWebDavServer: 1.0
HTTP/1.1: 423 Locked
Connection: close
Cache-Control: private
Content-Type: text/xml; charset=utf-8
Content-Length: 404
<?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:">guest</owner><a:locktoken>
<a:href>opaquelocktoken:eb56e920-3483-442a-a8b3-188c0394ab15:633441292022031250</a:href></a:locktoken>
<a:depth>0</a:depth><a:timeout>Second-180</a:timeout></a:activelock></a:lockdiscovery></a:prop>
---IIS自己的webdav的正确的Lock
HTTP/1.1 423 Locked
Connection: close
Date: Fri, 18 Apr 2008 06:35:12 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
Content-Type: text/xml
Content-Length: 396
<?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:B23DB6F1-294B-424C-9EFD-A64DC4402030:214748364852</a:href></a:locktoken><a:depth>0</a:depth><a:timeout>Second-180</a:timeout></a:activelock></a:lockdiscovery></a:prop>
回复 引用
找这方面的内容已经好长时间了,博主的文章非常有帮助.
我按照博主说的步骤操作,现在打开没有一点问题.
但保存时出现问题如下:
1、保存时没有出错,但内容根本保存不了(比较奇怪的是,保存后打开,文字内容现实不了,但光标的最后位置变化了)。
2、保存到数据库的内容特别小,只有100多字节,通常一个word文件应该有10000多字节的呀!
请博主帮忙看看我的代码,不胜感激!
public void ModifyDocContent(int docId, byte[] data)
{
string sConnectionString = "server=csharpgroup;uid=sa;pwd=csharpgroup;database=TestWebDB";
SqlConnection conn = new SqlConnection(sConnectionString);
SqlCommand InsertCommand = new SqlCommand();
InsertCommand.Connection = conn;
InsertCommand.CommandText = "UPDATE [Document] SET size=" + data.Length + ",Content = @Content WHERE Documentid=" + docId;
InsertCommand.Parameters.Add("@Content", SqlDbType.Image, data.Length, "Content"); //注意,此处参数Size为写入的字节数
//读取文件内容,写入byte数组
InsertCommand.Parameters["@Content"].Value = data; //为参数赋值
try
{
conn.Open();
InsertCommand.ExecuteNonQuery();
}
catch
{
throw new Exception("出现壹!");
}
finally
{
conn.Close();
}
}
回复 引用 查看