往往我们需要对一个web站点的文件进行管理,而常用两种方式:第一,通过ftp协议进行文件管理;第二,通过web页面提供的该功能(yahoo的公文包)。而这里将介绍第三种方式:FrontPage 服务器扩展(FrontPage Server Extensions Remote Procedure Call)。使用FrontPage 服务器扩展有以下优点:
l 使创作者们能协作创建和维护web站点、直接在服务器计算机上编辑一个 Web 站点(节省下载时间),并且在不必编写程序的情况下,轻易地在站点上加入新功能。
l 支持站点计数器、全文搜索、电子邮件表单处理程序和其他由创作者使用 FrontPage 添加到站点的功能。您不必下载、购买或安装独立的CGI兼容的程序来实现上述功能。
l 能在许多流行的服务器平台,如 Windows NT 和 UNIX,以及许多流行的站点服务器,如 Microsoft Internet Information Services (IIS)、Apache、WebSite 和 Netscape 上工作。
l 在一个 Web 站点中移动、删除或重命名一个网页(只是网页的文件名,而不是必须传送到站点服务器的整个文件的名称)后,自动更新超链接。
l 支持与 Microsoft Office、Visual SourceSafe 和 Index Server 的集成。
Frontpage RPC通过Frontpage RPC 方法来控制web站点,实现以上的功能。这里只是介绍如何通过Frontpage RPC对web站点的文件进行管理。对大家起一个抛砖引玉的作用。Frontpage RPC的是基于http协议的一种远程过程调用。Frontpage RPC通过http POST命令向web站点中特定的dll发送方法,而web服务器将返回带有操作结果信息HTML文档到客户端。客户端可通过该文档判断是否操作成功。
Frontpage RPC方法位于“method=”后,方法名后面有一个冒号“:”,它是方法名与Frontpage 服务器扩展的版本号的分隔符。版本号后面跟着该方法的参数。每个参数以记号“&”作为第一个字母,参数名紧跟“&”后面。参数名与参数的值用“=”连接,中间不能由任何的空格符号。可以参见下面的例子:
method=get document:server_extension_version
&service_name=/&document_name=service_relative_path/file_name
[&dir_name=directory_name][&effective_protocol_version=server_extension_version]
&old_theme_html=(true|false)&force=(true|false)[&doc_version=string]
&get_option=(none|0|1|2)&timeout=time_in_seconds[&validateWelcomeNames=(true|false)]
字体/代码
意义
例子
斜体
变量(参数)的值
service_relative_path/file_name
方括号([、])
可选的内容
[&validateWelcomeNames=(true|false)]
无格式文本
字面意义
method
分隔符(|)
分开可选的选项
(true|false)
接下来看看在这里将会使用到的Frontpage RPC 方法,具体的参数可以参看(http://msdn.microsoft.com/library/en-us/spptsdk/html/SPPTWSSFPSERPC_SV01072918.asp?frame=true):
l get document 获取指定的文档。
l list documents 列出位于指定url的web站点下文件、文件夹和子站点以及他们的meta-info。
l move document修改指定文件的文件名、移动指定的文件到指定的文件夹、复制指定文件。
l put document 上传一个文件或文件夹到已存在的web站点。
l remove documents 删除wen站点上指定的文件或文件夹。
在编码的过程中应该注意一下几点:
1. url编码问题:
l 在http协议中发送的数据主体必须是8位编码的字符,也就是只能由0-225之间的字符组成。中文字符是不能出现的。所以在所有Frontpage RPC中方法中所有的参数都需要经过url编码。url编码方式是把字符相对应的二进制的值转换为十进制,然后再前面加“%”。url中文的编码方式先将中文编码成对应的字节,然后在在前面加上“%”。比如“中国”使用UTF-8的编码是“228”,“184”,“173”,“229”,“155”,“189”六个字节组成,那么url的对应得编码应该是“%228%184%173%229%155%189”。如果“中国”使用gb2312的编码是“214”,“208”,“185”,“250”四个字节,那么对应的url编码应该是“%214%208%185%250”
l Frontpage RPC 中方法中所有的参数不能出现除数字和字母以外的字符,例如一般可以在地址栏看到的符号“.”、“_”都不允许出现在参数中。C#提供的UrlEncode方法不会对“.”、“_”这两个字符进行编码,所以我们需要写一个函数进行编码。
l 文件的url中如果带有“;”、“=”、“[”、“]”都需要转义为“\;”、“\=”、“\[”、“\]”才进行url编码。否则会提示找不到该文件。
2. html编码问题:html的编码与url相类似,只是html编码会将UTF-8“中国”编码为:“ä&# 184;&# 229;&# 155;&# 155;&# 189;”,gb2312编码为“Ö&# 208;&# 185;&# 250;”。
3. 注意方法中的大小写。这个问题我是在写这篇文章的时候,做演示工程的时候发现的问题。因为实际使用中可能是版本的问题,大小写与sdk中的有些出入。例如oldUrl正确的是参数名应该是oldUrl,而微软官方文档中的是oldURL。
4. Web站点和子站点的关系。曾经在开发的过程中,忽略的这个问题最后的直接后果是导致加班到晚上1点。在操作不同的子站点的时候,Frontpage RPC 方法发送的dll的url也是不一样的。而且子站点与子站点是独立的。不能够进行类似将子站点A的文件复制到子站点B这样的操作。
最后还是看看一些关键的代码:
using System;
using System.IO;
using System.Net;
using System.Web;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using System.Threading;
using System.Net.Sockets;
namespace FPSERPCClient
{
/// <summary>
/// Summary description for FPSEPublish.
/// </summary>
public class FPSERPCClient
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="username">用于登陆站点的用户名</param>
/// <param name="password">用于登陆站点的密码</param>
public FPSERPCClient(string username, string password)
{
Credential = new NetworkCredential(username, password);
}
/// <summary>
/// 删除web站点上指定的文件或文件夹。
/// </summary>
/// <param name="uri">删除文件的url</param>
/// <returns></returns>
public string RemoveDocument(string uri)
{
Uri myUri = new Uri(uri, true);
string webUrl, fileUrl;
UrlToWebUrl(uri, out webUrl, out fileUrl);
/// 这是最简单的一个方法
string postBody = String.Format("
method=remove documents&service_name={0}&url_list=[{1}]",
UrlEncode(webUrl),
UrlEncode(fileUrl));
return SendRequest(myUri.GetLeftPart(UriPartial.Authority) +
webUrl.TrimEnd('/') + "/_vti_bin/_vti_aut/author.dll", postBody);
}
/// <summary>
/// 发送Frontpage RPC方法的请求重载
/// </summary>
/// <param name="uri">发送请求的目标Url</param>
/// <param name="postBody">发送请求的主体</param>
/// <returns></returns>
private string SendRequest(string uri, string postBody)
{
return SendRequest(uri, postBody, null);
}
/// <summary>
/// 发送Frontpage RPC方法的请求重载
/// </summary>
/// <param name="uri">发送请求的目标Url</param>
/// <param name="postBody">发送请求的主体</param>
/// <param name="fileStream">下载文件保存的FileStream</param>
/// <returns></returns>
private string SendRequest(string uri, string postBody, Stream fileStream)
{
byte[] bPostBody = BodyEncoding.GetBytes(postBody);
return SendRequest(uri, bPostBody, bPostBody.Length, fileStream);
}
/// <summary>
/// 发送Frontpage RPC方法的请求重载
/// </summary>
/// <param name="uri">发送请求的目标Url</param>
/// <param name="postBody">发送请求的主体</param>
/// <param name="postLength">发送请求的主体长度</param>
/// <param name="fileStream">下载文件保存的FileStream</param>
/// <returns>返回接受的Response,为html格式的字符串</returns>
private string SendRequest(string uri, byte[] postBody, long postLength, Stream fileStream)
{
string strResponseText = null;
Stream sendStream = null;
HttpWebResponse response = null;
Stream receiveStream = null;
StreamReader responseTestReader = null;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
request.Headers.Add("X-Vermeer-Content-Type", "application/x-www-form-urlencoded");
request.UserAgent = "DRSERPCClient";
request.Accept = "auth/sicily";
request.Expect = "200-ok";
request.KeepAlive = true;
request.Credentials = this.Credential;
request.Timeout = HTTPREQUEST_TIMEOUT;
request.PreAuthenticate=true; // let's stop sending bunks of data prior to detecting 401
sendStream = request.GetRequestStream();
int iOffset = 0;
while(postLength > 0)
{
if (postLength > 4096)
{
sendStream.Write(postBody, iOffset, 4096);
postLength -= 4096;
iOffset += 4096;
}
else
{
sendStream.Write(postBody, iOffset, Convert.ToInt32(postLength));
break;
}
}
sendStream.Close();
response = (HttpWebResponse)request.GetResponse();
receiveStream = response.GetResponseStream();
int readBytes = 0;
byte[] readBuffer = new byte[BUFFER_SIZE];
///接受到的Response以“</HTML>\n”为分割符,后面的为下载文件的主体部分
while((readBytes = receiveStream.Read(readBuffer,0, readBuffer.Length)) != 0)
{
strResponseText += BodyEncoding.GetString(readBuffer, 0, readBytes);
int separatorOffset = strResponseText.ToUpper().IndexOf("</HTML>") + 8;
if(separatorOffset != 7)
{
strResponseText = strResponseText.Substring(0, separatorOffset);
if(fileStream != null)
{
fileStream.Write(readBuffer, separatorOffset % BUFFER_SIZE , readBytes - separatorOffset);
}
break;
}
}
///把下载文件的内容写入文件流中,最后保存在磁盘上
if(fileStream != null)
{
while((readBytes = receiveStream.Read(readBuffer,0, readBuffer.Length)) != 0)
{
fileStream.Write(readBuffer, 0, readBytes);
}
}
receiveStream.Close();
response.Close();
}
catch (Exception e)
{
if(sendStream != null)
sendStream.Close();
if(responseTestReader != null)
responseTestReader.Close();
if(receiveStream != null)
receiveStream.Close();
if(response != null)
response.Close();
throw e;
}
return strResponseText;
}
/// <summary>
/// 获取一个绝对Url的字站点地址,和相对该子站点的文件地址
/// </summary>
/// <param name="uri">绝对URL</param>
/// <param name="webUrl">子站点地址</param>
/// <param name="fileUrl">相对子站点的文件地址</param>
private void UrlToWebUrl(string uri, out string webUrl, out string fileUrl)
{
Uri myUri = new Uri(uri, true);
string strUriPartial = string.Empty;
for(int i = 0;i < myUri.Segments.Length; i++ )
strUriPartial += myUri.Segments[i];
string postBody = String.Format("method=url+to+web+url&url={0}&flags=0", UrlEncode(strUriPartial));
string response = SendRequest(myUri.GetLeftPart(UriPartial.Authority) + "/_vti_bin/shtml.dll/_vti_rpc",postBody);
webUrl = GetReturnValue(response, "webUrl");
fileUrl = GetReturnValue(response, "fileUrl");
}
/// <summary>
/// 获取一个返回的html中特定的值
/// </summary>
/// <param name="responseText">web站点返回的html字符串</param>
/// <param name="key">需要获取值得key</param>
/// <returns>返回该key的值 </returns>
public string GetReturnValue(string responseText, string key)
{
int start = responseText.IndexOf(key + "=");
if (-1 == start)
return null;
else
start += key.Length + 1;
int end = responseText.IndexOf("\n", start);
return HtmlDecode(responseText.Substring(start, end - start));
}
/// <summary>
/// 获取一组返回的html中特定的值
/// </summary>
/// <param name="responseText">web站点返回的html字符串</param>
/// <param name="key">需要获取值得key</param>
/// <returns>返回该key的值</returns>
public string[] GetReturnValues(string responseText, string key)
{
int start = 0;
int end = 0;
string returnValue = string.Empty;
ArrayList returnValuesList = new ArrayList();
while(true)
{
start = responseText.IndexOf(key + "=", end) + key.Length + 1;
if(start == key.Length)
break;
end = responseText.IndexOf("\n", start);
returnValue = responseText.Substring(start, end - start);
returnValuesList.Add(HtmlDecode(returnValue));
}
string[] returnValues = new string[returnValuesList.Count];
returnValuesList.CopyTo(returnValues);
return returnValues;
}
/// <summary>
/// url编码
/// </summary>
/// <param name="url">需进行编码的URL</param>
/// <param name="e">进行编码的类</param>
/// <returns>返回编码以后的值</returns>
public string UrlEncode(string url, Encoding e)
{
url = url.Replace(";", "\\;");
url = url.Replace("=", "\\=");
url = url.Replace("[", "\\[");
url = url.Replace("]", "\\]");
string encodedUrl = HttpUtility.UrlEncode(url, e);
encodedUrl = encodedUrl.Replace(".", "%2e");
encodedUrl = encodedUrl.Replace("_", "%5f");
return encodedUrl;
}
/// <summary>
/// url编码
/// </summary>
/// <param name="url">需进行编码的URL</param>
/// <returns>返回编码以后的值</returns>
public string UrlEncode(string url)
{
return this.UrlEncode(url, UrlEncoding);
}
/// <summary>
/// html编码
/// </summary>
/// <param name="html">需进行编码的html</param>
/// <param name="e">进行编码的类</param>
/// <returns>返回编码以后的值</returns>
public string HtmlDecode(string html, Encoding e)
{
string htmlDecoded = HttpUtility.HtmlDecode(html);
byte[] htmlDecodedBytes = Encoding.Unicode.GetBytes(htmlDecoded);
char[] htmlDecodedChars = Encoding.Unicode.GetChars(htmlDecodedBytes);
htmlDecodedBytes = new byte[htmlDecodedChars.Length];
for(int i = 0; i < htmlDecodedChars.Length; i++)
{
htmlDecodedBytes[i] = (byte)htmlDecodedChars[i];
}
htmlDecoded = e.GetString(htmlDecodedBytes);
return htmlDecoded;
}
/// <summary>
/// html编码
/// </summary>
/// <param name="html">需进行编码的html</param>
/// <returns>返回编码以后的值</returns>
public string HtmlDecode(string html)
{
return this.HtmlDecode(html, HtmlEncoding);
}
}
}