When developing Web applications, you often need to link to an external page programmatically. In some cases, you can accomplish this with a little help from the user by kindly requesting he or she click on a button to submit the contents of the current HTML form to an external page. Page redirection is another possibility. Normally, you redirect the user to a different page by using the Redirect method on the Response object. The Redirect method requires a round trip to the client. Basically, the method causes the Web server to return an HTTP 302 status code to the browser that indicates the new URL to request. As a result, you control the URL being accessed programmatically, but at the cost of an extra round trip. With Windows 2000 Server and ASP 3.0, Microsoft enriched the programming interface of the Server object with a pair of new methods: Execute and Transfer.
The Execute method executes a request to another page, then continues the execution of the original page after the new page is processed. The Transfer method provides a similar functionality but with a key difference: It passes the control of execution to the new page unconditionally. In other words, you use Execute if the external page is considered as a Sub or a Function in a piece of Visual Basic .NET code; you resort to Transfer if you need a full page redirection. Note, however, that Transfer implements the page redirection in a more effective way than Response.Redirect. In fact, the Transfer method doesn't post back to the browser but simply performs a server-side redirection.
In the transition to ASP.NET, both methods have been enhanced slightly to respond to user needs that have emerged in the meantime. In doing so, however, some gotchas have been introduced. In this article, I'll show you what became of both methods and what the recommended model of passing values from one page to the next is in ASP.NET. But before going any further, I should clarify a couple of things about these methods. Server.Execute and Server.Transfer were essential techniques in ASP 3.0, but they are less relevant in ASP.NET, in which you often can employ other techniques to solve the same issues. More often than not, you can devise the whole functionality from a wider perspective and solve it by using custom server controls and classes in what turns out to be a superior and more object-oriented approach. Yielding to external pages is still a valuable technique, especially if you're moving from ASP. In general, if you catch yourself using either Transfer or Execute too often, it's probably about time for you to rethink the functionality.
The Server.Execute Method
In ASP.NET, the functionality of ASP's intrinsic Server object is implemented by the HttpServerUtility class. An instance of this type is created when the ASP.NET runtime begins to process the request, then it is stored as part of the request context. The class provides a bunch of helper methods that are exposed publicly to modules and handlers through the HttpContext object's Server property. Modules and handlers include global.asax, Web pages, and Web services. In addition, to maintain ASP.NET coding as closely as possible to the ASP programming style, several other commonly used ASP.NET objects also expose their own Server properties. For example, both Page and HttpApplication show off Server properties that simply return that instance of the HttpServerUtility class.
Such an apparent redundancy of references has a subtle yet involved explanation. In ASP.NET, software entities - such as ASP's Server, Request, Response, and the like - are really more intrinsic properties than they are intrinsic objects. In ASP, they were instances of COM components published to the ActiveX Scripting engine with easy-to-remember names such as Server, Request, and Response. To make them visible throughout all levels of code blocks, the objects were marked as global and thereby gained the label of "intrinsically available objects."
In ASP.NET, the architecture is radically different. The specific functionalities have been maintained and even enhanced, but they now are integrated in the context of a truly component-based model. The visibility of an object in a certain scope depends on strict syntax rules. Wide-range visibility, such as that of intrinsic objects in ASP, can be obtained only by replicating the objects in all scopes in which they are needed. Hence, the concept of intrinsic properties.
Server.Execute does more or less the same in ASP.NET that it did in ASP. As mentioned, the method passes the control to the specified page for execution:
The child page executes like a subroutine, and the control is regained by the caller page as soon as the child request terminates. Any change to the surrounding environment is detected, and the context of the child request is the same as the original request. This technique is not recommended in 100 percent pure ASP.NET applications, in which using a middle-tier component is highly preferable. If you're migrating from an ASP system, however, this technique might save you some time and help you ship version 1.0 sooner. There's always a patch or service pack you can use to fix things up, right?
The big difference with ASP is all in the way in which you can treat the output of the child request. In ASP.NET, the HTML output generated by the child page can be flushed automatically in the parent-response buffer (as in ASP) or retained in a writer object. This second possibility leads me to think of Server.Execute as a sort of Visual Basic .NET Function call.
When the execution flow reaches the Server.Execute call, the control is passed to the specified page and the execution of the current page is suspended. The response text the child execution generates is captured and processed according to the particular overload of Execute that has been used:
|Public Sub Execute(url As String)</P><P>Public Sub Execute(url As String, _|
writer As TextWriter)
If a TextWriter object is specified, the response text of the child execution is accumulated into the writer and left at the disposal of the page. Otherwise, the response text is embedded in the response text of the main page automatically. For example, if you cache the output of the page, you can parse it to extract specific information. The URL you pass to execute can contain embedded parameters but must be part of the same domain. How can you pass values to the page that executes? As I mentioned, the query string is certainly an option:
Dim writer As StringWriter = New StringWriter()
Unlike what happens if you redirect using the Response.Redirect method, the browser in no way is involved with this operation - everything happens on the server. In the end, the browser simply receives an HTTP 200 status code (that is, all is OK) for the request it made to the original page.
The Server.Transfer Method
The Transfer method differs from Execute in that it terminates the current page after executing the specified one. The new page runs as if it were the page requested originally. One problem worth noting with the Server.Transfer method is that the URL in the browser is the URL of the original page, not the page to which the user has been transferred. This could result in some confusion for the user if the technique is misused.
The Transfer method has two overloads:
|Public Sub Transfer(url As String)</P><P>Public Sub Transfer(url As String, _|
preserveForm As Boolean)
The first overload redirects to the specified page; the second also clears or preserves the QueryString and Form collections, based on the value of the Boolean argument. When you call the first overload, the collections always are cleared unless the IsPostBack property on the original page is set to True.
The Execute and Transfer methods have similar architectures. Both methods use the same internal procedure to execute the call. When you call Transfer, the new page request is served immediately. Transfer can control programmatically whether the QueryString and Form collections are to be preserved. The previous request is terminated, however, only when the internal call used to implement the redirect has returned. All the code that might follow the call to Transfer in the caller page never executes.
Neither Transfer nor Execute causes a round trip to the client. More importantly, to carry on the child request, they use the same HttpApplication that was serving the original request. This makes the child request somewhat of a lightweight request because it limits the impact on the ASP.NET infrastructure. But this same feature has a drawback that might be even more critical to your system's health. When you use either Transfer or Execute to redirect to a page the user is not authorized to view, the page is processed anyway. Instead of making another request from the server, which would force reauthorization, both methods use a different handler within the same HttpApplication to process the page. You gain direct access to the page regardless of any security barrier placed in between. To avoid this flaw (which probably will be addressed in the next version of ASP.NET), you should check the role of the user before you yield the control. Dropping Execute and Transfer in favor of, say, Response.Redirect is another solution. (This problem also has been addressed in article Q320976 of Microsoft's Knowledge Base: http://support.microsoft.com.)
If you're going to invoke an external page, chances are you'll have to pass values, too. The query string, Session, or HttpContext of the calling page are all good places to store values the child page retrieves. An alternative approach is based on the fact that in ASP.NET, each running page is an instance of a particular class. Subsequently, a call across pages actually is resolved in a class-to-class call. How can the caller page pass values down to the callee? You can expose the values through public properties on the caller page and reference the assembly of the caller page into the run-time environment of the callee.
The first step is to define an alias for the class that represents the caller page. The base class for running an ASP.NET page is Page, and all classes actually used Inherit, directly or indirectly, from that. Whatever the base class is, the real name of the class that represents the running page responds to a precise pattern: ASP.file_aspx, where "file" is the page's filename. From within the child page, the caller page is seen and managed as an instance of the ASP.file_aspx class. An alias allows you to use a more evocative name. You define a page's class alias by using the ClassName attribute on the @Page directive:
|<%@ Page ClassName="SearchPage" %>|
Figure 1 illustrates a page - Search.aspx - that contains a public property and performs the search using an internal page named Do_Search.aspx. The output then is gathered and inserted in the existing user interface.
<%@ Page Language="VB" ClassName="SearchPage" %>
<%@ Import Namespace="System.IO" %>
Public ReadOnly Property TextToSearch() As String
End Property</P><P>Protected Sub Search(sender As Object, e As EventArgs)
Dim writer As StringWriter = New StringWriter()
Results.Text = writer.ToString()
id="searchText" text="ASP.NET" />
<asp:button runat="server" onclick="Search" text="Go" />
<asp:label runat="server" id="Results" />
Figure 1. The page exposes some values as public properties. A child page run through Execute or Transfer can retrieve them using a direct, strong-typed binding.
Aside from the public property that exposes the string to search for, the ClassName attribute is the only special aspect of the search.aspx page. Note that the ClassName attribute doesn't change the name of the type being created for an instance of the page. It represents only an additional piece of information for the ASP.NET runtime.
The ASP.NET model of passing arguments from page to page is based on the assumption that the child page references the assembly that contains the caller page's class. A page's assembly is referenced using the @Reference directive. For example, you reference the assembly that defines search.aspx using this code:
|<%@ Reference Page="search.aspx" %>|
The @Reference directive is an indirect way of linking the page to the assembly that is dynamically generated for a requested ASPX page. The name of that assembly is random, not known in advance and not foreseeable. The @Reference directive links to the assembly for the specified page, whatever its run-time name is. After that, you can declare a global variable of the type set in the ClassName alias:
Protected m_callerPage As SearchPage
If you didn't set the ClassName attribute, you must use the real name of the class - in this case, ASP.search_aspx. The final step is associating the variable name with a valid instance of the SearchPage class. Nicely enough, ASP.NET maintains a living instance of the caller page in the Handler property of the request context. You access the context of the current request by using the Context property on the Page class:
|m_callerPage = CType(Context.Handler, SearchPage)|
At this point, you hold a valid instance of the class that represents the caller. Everything that is declared public in the class is freely accessible. Figure 2 shows the source code of the do_search.aspx page that retrieves some input text and performs the search.
|<%@ Page Language="VB" %>|
<%@ Reference Page="search.aspx" %></P><P><script runat="server">
Protected m_callerPage As SearchPage</P><P>Sub Page_Load(sender As Object, e As EventArgs)
m_callerPage = CType(Context.Handler, SearchPage)
End Sub</P><P>Function GetResults(textToSearch As String) As String
Response.Write("<h3>Results for '")
Figure 2. The child page binds to the caller page using the Handler property on the HttpContext object. After that, it retrieves any values the parent class exposes publicly. For this technique to work, though, you must know about the caller.
Note that this method assumes the called page knows about the type and programming interface of the caller. If you find this constraint too limiting, consider adding the arguments to the HttpContext object. Then, the callee simply could pull the value out of the HttpContext without knowing anything about which page put it there:
Although less elegant, this solution is equally effective and allows the executed page to be called from anywhere. HttpContext is preferable to Session because the context is a buffer of memory with a limited lifetime, meaning it doesn't survive the current request.
When I first tested the code in this article, I was working with a beta of ASP.NET 1.1 already, and it worked great. Later, I tested it with ASP.NET 1.0, but a message popped up about the view state being invalid or corrupted. Making things more intricate and intriguing was the fact that the error seemed to affect Execute but not Transfer.
As I mentioned, both the main and the child pages are run by the same HttpApplication object because they were the same request. What happens under the hood is a sort of context switch. First, the internal method used in both cases obtains an HTTP handler from the application factory and prepares to serve the new request. The original handler of the main request is cached (the Context.Handler property) and replaced with the new handler. The spawned page inherits the context of the parent; when finished, any modification made to Session or Application is immediately visible to the parent page.
The handler switch makes the whole operation extremely fast because there's no need to create a new object to serve the request. When the child page returns, the original handler is restored. The execution of the main page continues from the point at which it was stopped, but it uses the context inherited from the child page. This implementation speeds up execution but might lead to the aforementioned security flaw if no countermeasures are taken. Unlike Execute, the Transfer method doesn't preserve the contents of the QueryString and Form collections by default. (You can control this parameter by using one of the Transfer overloads.) For this reason, when the page handler processes the new sub-request that Execute originates, the Form collection still contains the view state of the original page. By default, the page handler always checks the view state to detect possible corruption. This behavior is controlled by the EnableViewStateMac attribute that, for security reasons, you should never disable. If the Form collection is not cleared, the handler attempts to use the view state of the original page to validate the child page. Because view state is page-scoped and can't be transferred across pages, the check fails and originates the view-state corruption warning. This doesn't happen with Transfer because the method clears the Form collection and query string by default. It also would fail for Transfer if this overload were used:
This is a bug in ASP.NET 1.0 that has been fixed in ASP.NET 1.1. For more information, see Microsoft's Knowledge Base article 316920. Note that the article doesn't mention Execute, but you can reproduce the problem easily by running this article's sample code.
Dino Esposito is a trainer and consultant who specializes in ASP.NET, ADO.NET, and XML. A member of Wintellect and co-founder of http://www.VB2TheMax.com, Dino wrote Building Web Solutions with ASP.NET and ADO.NET and the upcoming Programming ASP.NET, both from Microsoft Press. Write to him at mailto:email@example.com.
Yield to Pages in ASP.NET
Although in ASP.NET you often can devise your application to do without the Execute or Transfer methods, these are valuable techniques, especially if you're building from the ashes of an existing ASP application. You can pass values to an external page in a variety of ways.
If you take a look at the source code that accompanies this article, you'll see you can spawn the child page either by using Transfer or Execute. If you use Execute and run the code in ASP.NET 1.0, you'll have a view-state corruption error. Run the same code on a machine on which you have ASP.NET 1.1 installed (even the beta build 1.1.4322), and it will work great. If you replace the call to Execute with an identical call to Transfer, it'll work both in ASP.NET 1.0 and ASP.NET 1.1.
Unfortunately, there's no known way to force Execute to work the right way in ASP.NET 1.0. If Transfer simply doesn't work for you, the only safe alternative I can think of is rewriting the code not to use external pages.