位于System.Net名称空间里边的WebClient,其实很好用,下载和上传等常规动作都可以完成,是居家旅行的佳品。可最近在使用的时候,才发现这个宝贝竟然没有Timeout属性,这实在让人苦恼。google了一下,发现一些人已经很早就发出这个疑问了,可各位大拿的答案也总是那么的Cool: 用WebRequest和WebResponse代替WebClient吧! 这固然是个解决的方法,但直接使用这两个底层对象操纵的话,代码要复杂好多,那些DownloadData,DownloadFile,OpenRead,OpenRead....等等函数都没得用了,好生可惜。
实在不甘心,那就为WebClient对象添加一个Timeout属性吧。首先我想到的是从WebClient中继承一个新的对象出来,这个想法持续了两秒,看到public sealed class WebClient : System.ComponentModel.Component 这句碑文就嘎然而止了。然而sealed如何抵挡得住Reflector的洪流呢,干脆我把WebClient整个类反编译了出来,好在也不大,才24K。
由于WebClient是.NET Framework里边的类,逆向工程后必需做一点点整容。首先名称空间不能再属于
System.NET了,随便改一个别的名称空间名字。然后编译一看,呵呵,出现了10多个错误,其实主要是调用.NET Framework内部资源,因为现在它下海了,再不能享受这些Internal的资源了。(反向.NET FW内置的对象都会遇到这样唏嘘的问题)改也非常简单,比如这个函数:
private void CopyHeadersTo(WebRequest request)
{
if ((this.m_headers != null) && (request is HttpWebRequest))
{
string text1 = this.m_headers["Accept"];
string text2 = this.m_headers["Connection"];
string text3 = this.m_headers["Content-Type"];
string text4 = this.m_headers["Expect"];
string text5 = this.m_headers["Referer"];
string text6 = this.m_headers["User-Agent"];
// this.m_headers.RemoveInternal("Accept"); //Reflector
// this.m_headers.RemoveInternal("Connection");
// this.m_headers.RemoveInternal("Content-Type");
// this.m_headers.RemoveInternal("Expect");
// this.m_headers.RemoveInternal("Referer");
// this.m_headers.RemoveInternal("User-Agent"); //Reflector
this.m_headers.Remove("Accept"); //手工修改
this.m_headers.Remove("Connection");
this.m_headers.Remove("Content-Type");
this.m_headers.Remove("Expect");
this.m_headers.Remove("Referer");
this.m_headers.Remove("User-Agent"); //手工修改
request.Headers = this.m_headers;
if ((text1 != null) && (text1.Length > 0))
{
((HttpWebRequest) request).Accept = text1;
}
if ((text2 != null) && (text2.Length > 0))
{
((HttpWebRequest) request).Connection = text2;
}
if ((text3 != null) && (text3.Length > 0))
{
((HttpWebRequest) request).ContentType = text3;
}
if ((text4 != null) && (text4.Length > 0))
{
((HttpWebRequest) request).Expect = text4;
}
if ((text5 != null) && (text5.Length > 0))
{
((HttpWebRequest) request).Referer = text5;
}
if ((text6 != null) && (text6.Length > 0))
{
((HttpWebRequest) request).UserAgent = text6;
}
}
}
改完这个函数后,只剩下几个内部资源的错误了,比如:
try
{
...
}
catch (Exception exception1)
{
if (!(exception1 is WebException) && !(exception1 is SecurityException))
{
//throw new WebException(SR.GetString("net_webclient"), exception1); //Reflector
throw new WebException("net_webclient", exception1); //手动更改
}
throw;
}
不要使用SR对象就万事OK。
这样错误都搞掉了,WebClient已经翻版出来了,添加Timeout就很容易了吧,如下几句:
private const int noTimeout = -1;
/// <summary>
/// 超时毫秒数,默认负1
/// </summary>
private int timeout = XWebClient.noTimeout;
/// <summary>
/// 超时毫秒数
/// </summary>
public int Timeout
{
get
{
return this.timeout;
}
set
{
this.timeout = value;
}
}
/// <summary>
/// 带超时设置的WebClient
/// </summary>
/// <param name="timeout">超时毫秒数</param>
public XWebClient(int timeout)
{
this.timeout = timeout;
}
/// <summary>
/// 创建带超时设置的WebRequest对象
/// </summary>
/// <param name="uri"></param>
/// <returns></returns>
private WebRequest createTimeoutWebRequest(Uri uri)
{
WebRequest wreq = WebRequest.Create(uri);
if (this.timeout != XWebClient.noTimeout) //加上超时设置
wreq.Timeout = this.timeout;
return wreq;
}
加了之后,我们要在整个类中查找WebRequest.Create字符串,把原来的代码替换成调用createTimeoutWebRequest函数的,我们就完成任务啦。比如下面:
public byte[] DownloadData(string address)
{
byte[] buffer1;
try
{
this.m_responseHeaders = null;
WebRequest request1 = this.createTimeoutWebRequest(this.GetUri(address)); //加上超时设置
request1.Credentials = this.Credentials;
this.CopyHeadersTo(request1);
WebResponse response1 = request1.GetResponse();
this.m_responseHeaders = response1.Headers;
buffer1 = this.ResponseAsBytes(response1);
}
catch (Exception exception1)
{
if (!(exception1 is WebException) && !(exception1 is SecurityException))
{
//throw new WebException(SR.GetString("net_webclient"), exception1);
throw new WebException("net_webclient", exception1);
}
throw;
}
return buffer1;
}
到此,我们就基本达到目标了,经过测试,暂时没发现什么后遗症。当然,这样做也是有代价的,比如没有了原本函数对象的XML Comment了,不过能用就好。有兴趣甚至可以把超时做成一个event委托出去,在多线程使用场景中可能更方便:)
最后BTW,看了看vs2005 CTP beta 2,里边的WebClient好像仍然没有Timeout属性的。
涂鸦之作,见笑。