5.6 发送HTTP POST请求给ASP.NET Web应用程序
问题
如何发送HTTP POST请求给ASP.NET Web应用程序并取回对应的响应。
设计
创建一个HttpWebRequest对象。把这个对象的“Method”属性设为“POST”并且把ContentType属性设为“application/x-www-form-urlencoded”。把Web程序的ViewState值加到POST数据字符串的后面。如果你的Web应用程序是基于ASP.NET 2.0的,你还必须把Web程序的EventValidation值也加到POST数据的后面。然后,通过GetRequestStream()方法和Stream.Write()方法把POST数据添加到要发送的请求。接下来,使用HttpWebRequest.GetResponse()方法取回对应的HTTP响应。
方案
string url = "http://localhost/TestAuto/Ch5/WebForm.aspx";
string data = "TextBox1=red&TextBox2=empty&Button1=clicked";
string vs = "dDwtMTQwNDA4NDA4ODs7PeWiylVlaimBKuqooykeHvDojL2i";
vs = HttpUtility.UrlEncode(vs);
data += "&__VIEWSTATE=" + vs;
byte[] buffer = Encoding.ASCII.GetBytes(data);
HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.ContentLength = buffer.Length;
Stream reqst = req.GetRequestStream();
reqst.Write(buffer, 0, buffer.Length);
reqst.Flush();
reqst.Close();
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
Stream resst = res.GetResponseStream();
StreamReader sr = new StreamReader(resst);
Console.WriteLine("\nGrabbing HTTP response:\n");
Console.WriteLine(sr.ReadToEnd());
sr.Close();
resst.Close();
Console.WriteLine("Done");
注解
设想有一个名为WebForm1.aspx的ASP.NET Web程序如下:
<html>
<head>
<script language="c#" runat="server">
void Button1_Click(object sender, System.EventArgs e)
{
if (TextBox1.Text == "red")
TextBox2.Text = "Roses are red";
else if (TextBox1.Text == "blue")
TextBox2.Text = "The sky is blue";
else
TextBox2.Text = "unknown color";
}
</script>
</head>
<body>
<h3>Color Commenter</h3>
<form id="Form1" method="post" runat="server">
<p>Enter color:
<asp:TextBox id="TextBox1" runat="server"/></p>
<p>My comment:
<asp:TextBox id="TextBox2" runat="server" /></p>
<p><asp:Button id="Button1" text="Send"
onclick="Button1_Click" runat="server" />
</form>
</body>
</html>
这个Web程序是通过手工创建的,而不是通过Visual Studio .NET生成的,如果采用后者,用C#写的逻辑代码和用于HTML显示的代码会分开在不同的文件里。手工创建代码并不会影响自动化测试程序的运行。这个Web程序有两个文本框和一个按钮控件。用户在TextBox1控件里输入一个类似于“red”的字符串,然后单击Button1控件向Web服务器发送HTTP请求。ASP.NET Web服务器检查TextBox1的字符串值并且创建一个HTTP响应页面在TextBox2里显示类似于“Roses are red”这样的一条消息。上述方案的代码执行以后,输出结果如下:
Sending TextBox1=red
Grabbing the HTTP response:
<html>
<head>
</head>
<body>
<h3>Color Commenter</h3>
<form name="Form1" method="post" action="WebForm.aspx"
id="Form1">
<input type="hidden" name="__VIEWSTATE"
value="dDwtMTQwNDA4NDA
4ODs7PuWdy3VjanmrKIqoo7kBHkDzjH2p" />
<p>Enter color:
<input name="TextBox1" type="text" value="red"
id="TextBox1" /></p>
<p>My comment:
<input name="TextBox2" type="text" value="Roses are red"
id="TextBox2" /></p>
<p><input type="submit" name="Button1" value="Send"
id="Button1" />
</form>
</body>
</html>
Done
请注意,带有input标签的TextBox2的值为“Roses are red”。直接向ASP.NET Web程序发送HTTP请求并且取回响应,最灵活的方法就是使用HttpWebRequest类。首先准备好用于发送的数据,这些数据以“名字-值”字符串的形式存在:
string data = "TextBox1=red&TextBox2=empty&Button1=clicked";
“TextBox1=red”是显而易见的。待发送数据中的“TextBox2=empty”和“Button1=clicked”部分是为了让ViewState值在客户端的自动化测试程序和ASP.NET Web服务器之间保持同步。每个ASP.NET Web程序都有一个ViewState值,这个值代表每次“请求-响应”完成之后程序的状态。ViewState值是基于Base64编码的一个字符串。通过创建并且维护一个ViewState值,Web服务器可以在连续的HTTP请求之间保持程序的状态。你必须要确定ViewState的值并且把它添加到待发送数据:
string vs = "dDwtMTQwNDA4NDA4ODs7PuWdy3VjanmrKIqoo7kBHkDzjH2p";
vs = HttpUtility.UrlEncode(vs);
data += "&__VIEWSTATE=" + vs;
因为ViewState的值有可能包含一些ASP.NET Web难以处理的特殊字符(比如“&”),我们应该针对ViewState的值使用HttpUtility.UrlEncode()方法来处理这种情况。HttpUtility类位于System.Web命名空间里,这个命名空间在默认情况下对于控制台程序来说是不可访问的,所以你必须要添加一个到System.Web.dll(详细信息请参见5.7节)的工程引用。提醒一下,VIEWSTATE前面有两个下划线。有两种方法可以确定ViewState的初始值。第一种(也就是我们这里用到的)是通过手动运行IE(或其他客户端程序),加载WebForm.aspx程序,然后选择View ➤ Source。第二种方法是通过程序来发送一个HTTP请求给WebForm.aspx,然后再通过程序从返回的HTTP响应中取出ViewState的值。这种技术将在5.8节进行讲解。
并没有完整的文档告诉我们,具体有哪些ASP.NET程序中的组件参与ViewState的编码以及ASP.NET Web服务器是如何计算ViewState的值的,所以说我们需要通过试验和纠错来确定到底应该在待发送字符串里放上哪些值。比如说,在本例中,我们可以去掉待发送字符串中的“TextBox2=empty”, 但是“Button1=clicked”是必需的。关于字符串“empty”和“clicked”并没有什么严格的规定。换句话说,我们也可以写成“Button1=foo”,甚至是“Button1=”,而ViewState的值仍然会保持同步,并且自动化测试程序还是能够照常运行。使用象“empty”和“clicked”这样的字符串常量,可以让代码更具可读性,但也有可能会误导代码的复查者以为这些值有什么特殊的含义。当添加ViewState到待发送字符串时,ViewState值的位置并不是固定死的。但是,把ViewState的值加在待发送字符串的最后可以让代码更具可读性。
在ASP.NET2.0里,新加入了一个EventValidation特性用来防止回送欺骗性的信息。程序框架发送的是加密后的数据,这些数据作为__EVENTVALIDATION隐藏字段的一部分。这个隐藏字段作为Web页面表单(form)的最后一个元素。所以在ASP.NET 2.0环境下,必须像下面这样把EventValidation值添加到POST数据中:
string ev = "d+waMTswVDA4NDA4OQs7buWdy3VwbjkrKIqoo7kBHkDzjH2p";
ev = HttpUtility.UrlEncode(ev);
data += "&__EVENTVALIDATION=" + ev;
构建好这个用于POST的数据字符串之后,必须通过System.Text.Encoding.ASCII.GetBytes()方法把它转换成一个字节数组,因为HTTP数据是以字节的形式进行传输的。接下来,必须把request对象的Method属性设为“POST”,把ContentType属性设为“application/x-www-form- urlencoded”。ContentType是一个字符串类型的值,它告诉Web服务器与这个HTTP请求相关的数据应该被解释为HTML表单数据。然后,需要把ContentLength属性设为字节数组中待发送数据的长度。准备好这个请求之后,可以通过HttpWebRequest.GetRequestStream()方法获得这个请求的数据流,然后把待发送数据添加到这个HTTP request对象的数据流。
reqst.Write(buffer, 0, buffer.Length);
我们需要指定把哪个字节数组写到数据流,以及把字节数组的第几个元素作为起始位置和要写入的字节数。做完这些准备工作之后,接下来就可以发送HTTP请求并取回HTTP响应:
HttpWebResponse res = (HttpWebResponse)req.GetResponse();
Stream resst = res.GetResponseStream();
然后我们可以使用一个StreamReader对象取回响应。这里讲述的技术对于检查由ASP.NET Web应用程序发回的HTTP响应是很有用的,但是要把这种方案扩展为实用的自动化测试程序,则必须根据给定的期望值在返回的响应中进行查找。
如果需要处理有代理服务器的情况,只要为HttpWebRequest对象添加可选的Proxy属性就可以了:
// 此处初始化 HttpWebRequest
string proxy = "someProxyMachineNameOrIPAddress";
req.Proxy = new WebProxy(proxy, true);
把代理服务器的机器名或者IP地址作为字符串传给WebProxy的构造函数,并且把生成的WebProxy对象赋给HttpWebRequest对象。第二个参数是布尔型变量,它指定本地地址是否忽略代理服务器设置;如果设为true,则本地地址忽略代理服务器设置。
通过把本节的代码重构为如下的一个辅助方法,我们可以大大提高自动化测试程序的模块化程度:
private static bool ResponseHasTarget(string uri,
string postData,
string target)
{
// 创建HttpWebRequest
// 把postData添加到请求数据流
// 获得HttpResponse 数据流
// 把响应连接到StreamReader对象sr
string result = sr.ReadToEnd();
if (result.IndexOf(target) >= 0)
return true;
else
return false;
}
这个辅助方法有三个参数,依次是:指向Web程序的URL(比如“http://server/path/ WebForm.aspx”),待发送的数据(比如“TextBox1=red&TextBox2=blue”),以及一个目标字符串(比如“The result is purple”)。如果与发送的HTTP请求对应的HTTP响应包含目标字符串,则这个方法返回true,如果目标字符串不在返回的HTTP响应里,则返回false。5.12节的示例程序包含ResponseHasTarget()的完整实现。







