Programmatic ASP.Net Post Back

Introduction

This post is somewhat related to my last post that discussed using the Web Dev Server, that ships with VS, to hit ASP.Net web pages from a unit test. In this post I’m going to cover how to fake a user initiated post back from C# code, this example will mimic a button click but should be easy enough to adapt to another control.

So when I came up with this approach I wanted to test that a particular class could be successfully serialized into the View State of a page and then successfully de-serialized in a post back. This is interesting because the view state is maintained in the request/response, rather than on the server, me we need to simulate the manner in which a browser would transfer this information.

Setting up a Web Page

So the starting point is to get an ASP.Net page that can test out the functionality we are after and report success or failure. This is simple enough, fire up Visual Studio (I’m using 2008 SP1) and create a new ASP.Net Application (File -> New -> Project -> Web -> ASP.Net Web Application), I’m calling mine “PostBackDemo”

I’m just going to work with the Default.aspx page that is created for you and adding a single button to the page, I’m leaving it aptly named “Button1”. If you double click on the button VS will drill through to the code behind where you should now have two methods, one that runs on page load and another when Button1 is clicked.

At this point I’m going to add in a class to test, sticking with original scenario I’m going to verify that am instance of this class can be placed into View State and pulled back out on a post back. I’m adding in a very uninteresting Person class that has two string properties as shown below, note that I’ve marked it as serializable otherwise we won’t be able to put into View State.

[Serializable]
public class Person
{
   public string FirstName { get; set; }
   public string LastName { get; set; }
}

Now I’m going to fill out the code file for my ASPX page to add a person into View State when a page is requested and to read it back out when Button1 is clicked. The page will then write out [PASS] or [FAIL] and send the response back.

using System;

namespace PostBackDemo
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            //Load a new person into View State on intial request
            if (!Page.IsPostBack)
            {
                Person test = new Person();
                test.FirstName = "Rowan";
                test.LastName = "Miller";
                ViewState["CURRENT_PERSON"] = test;
            }
        }

        protected void Button1_Click(object sender, EventArgs e)
        {
            //Try and pull a person back out of View State
            Person test = ViewState["CURRENT_PERSON"] as Person;

            //Check if the person came back OK
            if (test != null
                && test.FirstName.Equals("Rowan")
                && test.LastName.Equals("Miller"))
            {
                Response.Write("[PASS]");
            }
            else
            {
                Response.Write("[FAIL]");
            }

            //Send outcome back to requester
           Response.End();
        }
    }
}

At this point you should be able to run your project and when clicking the button a simple “[PASS]” should be written out for you.

Writing an Automated Test

We are going to create a unit test using the inbuilt test framework in Visual Studio, for the purpose of this demo our test is going to rely on the page being available on a given url but if you combine this post with my last post on hosting pages within a unit test using Web Dev Server you can accomplish a truly automated solution.

So I’m just adding a new Test Project to my solution (File -> Add -> New Project -> Test -> Test Project), I’m calling mine TestProject. I’m just going to add my test code into the TestMethod1() that is created for you in UnitTest1.cs. Now the code to achieve what we want is actually pretty simple, I’ve posted a few topics now that use Systme.Net.HttpWebRequest and System.Net.WebResponse to hit resources over http, this one is the same except that we need to extract some content out of the initial response and include it when we post back.

The code and comments pretty much cover it so I’ll just point out a couple of things;

  • The initial response contains two hidden controls called __VIEWSTATE and __EVENTVALIDATION we simply need to copy the content of these controls and include them in our second request. View State is pretty obvious and includes the serialized version of our person instance. Event Validation isn’t so obvious, this is an internal construct of ASP.Net that is used to help guarantee the integrity of the page content, you can read more on that topic at http://msdn.microsoft.com/en-us/magazine/cc163512.aspx.
  • You’ll also notice that I’m including “&Button1=Button1” at the end of the content, this simply identifies that Button1 was clicked and ASP.Net raises the ‘Clicked’ event on Button1 for us when it sees this.
  • The other noteworthy part of the code is the use of a delegate to perform the writing of content to the post back request stream, you could equally have this logic tucked into a method somewhere.
    If you had a method defined such as public void WriteContent(IAsyncResult asynchronousResult)  you could simply call IAsyncResult res = request.BeginGetRequestStream(new AsyncCallback(WriteContent), request);

So with all that covered here is my test code;
Note that you will need to add a reference to System.Web and System.XML in your test project and the corresponding using statements at the top of UnitTest1.cs.
I’ll also just reiterate that you will need to have the aspx page available and replace the first string variable in the code below with the url of your page, if you want your test to deal with spinning up the page see my last post.

[TestMethod]
public void TestMethod1()
{
    //The url of the page you want to test
    string url = "<a href="http://localhost:49907/default.aspx">http://localhost:49907/default.aspx</a>";

    //Perform an initial request of page
    HttpWebRequest reqInitial = (HttpWebRequest)WebRequest.Create(url);
    WebResponse resInitial = reqInitial.GetResponse();

    //Extract the ASP.Net content from the page
    string viewstate = "";
    string eventvalidation = "";
    XmlTextReader xmlRead = new XmlTextReader(resInitial.GetResponseStream());
    while (xmlRead.Read())
    {
        if (xmlRead.Name == "input"
            && xmlRead.GetAttribute("id") == "__VIEWSTATE")
        {
            viewstate = xmlRead.GetAttribute("value");
        }
        else if (xmlRead.Name == "input"
           && xmlRead.GetAttribute("id") == "__EVENTVALIDATION") 

        {
            eventvalidation = xmlRead.GetAttribute("value");
        }
    }

    //Build the ASP.Net content to be included in the postback
    string postBackContent = string.Format("__VIEWSTATE={0}&__EVENTVALIDATION={1}",
      HttpUtility.UrlEncode(viewstate),
      System.Web.HttpUtility.UrlEncode(eventvalidation));

    //Add the content to notify Button1 was clicked
    postBackContent += "&Button1=Button1";

    //Build the post back request
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
    request.Method = "POST";
    request.Referer = url;
    request.ContentType = "application/x-www-form-urlencoded";

    //Create a delegate to send content into the request
    //You would probably implement this as a seperate method that could be re-used
    AsyncCallback contentLoader = delegate(IAsyncResult asynchronousResult)
    {
        //Pull request out of async state
        HttpWebRequest req = (HttpWebRequest)asynchronousResult.AsyncState;

        //Turn our content into binary data and send it down the request stream
        byte[] content = Encoding.UTF8.GetBytes(postBackContent);
        System.IO.Stream postStream = request.EndGetRequestStream(asynchronousResult);
        postStream.Write(content, 0, content.Length);
        postStream.Close();
    };

    //Use the delegate to pour our content down the request
    IAsyncResult res = request.BeginGetRequestStream(contentLoader, request);
    //We are going to lock while we wait for a response but you could perform other
    //operations while the request is processed
    res.AsyncWaitHandle.WaitOne();

    //Pull the final content back out of the response
    WebResponse resPostback = request.GetResponse();
    StreamReader streamFinalContent = new StreamReader(resPostback.GetResponseStream());
    string result = streamFinalContent.ReadLine();

    Assert.AreEqual("[PASS]", result);
}

The Exploration Process

Coming up with this approach code was actually quite simple, once I had the web page up and running I used an HTTP tracing utility to work out what was getting sent to and from my browser and then just wrote code to mimic that. I was using Http Debugger Pro from http://www.httpdebugger.com/ but there are plenty of alternatives out there.

Conclusion

If you want to mimic user interactivity with an ASP.Net web page then you can achieve this pretty simply using functionality shipped with .Net in the System.Net assembly. This can be useful for lots of reasons including automated end to end testing of web based scenarios.

As always you can test most things using simplified approximations of the real world scenario, for example testing that classes can be serialized and de-serialized with binary serialization could be achieved in a couple of lines of code and would suggest that they will behave correctly in View State. But nothing beats an automated end to end test to make sure they really work… and stay that way 🙂

Advertisements