简体   繁体   中英

Server.Transfer, Response.Redirect, and ApplicationInstance.CompleteRequest work around in ASP.Net

I am trying to add async features to an existing asp.net forms application. But I am running into problems with page transfer/redirection. Reason being in much of the existing code redirects/transfers are made from utility classes or custom controls etc. Also, have code that exists after the redirect/transfer further up the stack that needs to be avoided. Original code took advantage of redirect/transfer internally calling Response.End and short circuiting out of the code execution for the thread via the ThreadAbortException.

For Server.Transfer and Response.Redirect, pages that have been made asynchronous by adding Async="true" have the ThreadAbortException bubble up and the browser ends up with an error response. I am assuming that since the page is async the current page request finishes before the transfer page, instead of the sync way of thread abort, then would start processing the transfer page.

When I use Response.Redirect( "[somepagename]", false) or Server.Execute, they prevent the current response from ending so I add ApplicationInstance.CompleteRequest after them, which skips later event and handler pipeline processing. But code after the redirect/execute continues which causes other errors/issues. (For example calling to Response.Redirect again to go to a different page based on logic that would have been skipped originally by the ThreadAbortException.)

I also tried Response.FlushAsync between Server.Execute and ApplicationInstance.CompleteRequest which looks ok to the browser. Exceptions after CompleteRequest don't show to the browser (at least in dev) since FlushAsync sends the buffered response from the transfer page. But that still might have race condition as to if the flush or continued code execution of the first page finish first. And subsequent code after CompleteRequest is still executed which may have side effects.

Original code:

public static void GoToPage( string page, bool transfer )
{
    if ( transfer ){HttpContext.Current.Server.Transfer( page, false ); }
    else { HttpContext.Current.Response.Redirect( page ); }
}

I have a solution. I changed the Server.Transfer portion of the call to:

if ( ( HttpContext.Current.Handler as Page )?.IsAsync ?? false )
{

    if ( transfer )
    {
        HttpContext.Current.Server.Execute( page, false );
        HttpContext.Current.Response.Flush();
        Thread.CurrentThread.Abort();
    }
    else
    {
        HttpContext.Current.Response.Redirect( page, false );
        Thread.CurrentThread.Abort();
    }
}
else
{
    if ( transfer )
    {
        HttpContext.Current.Server.Transfer( page, false );
    }
    else
    {
        HttpContext.Current.Response.Redirect( page );
    }
}

Then updated the async click even handler to catch the ThreadAbortException. Exception handler calls ResetAbort and CompleteRequest.

private async void SubmitButton_Click( object sender, EventArgs e )
{
    try
    {
        //Awaited async call that returns a value.

        //Call to utility classes that eventually call GoToPage( string page, bool transfer )

    }   
    catch ( ThreadAbortException tEx )
    {
        Thread.ResetAbort();
        HttpContext.Current.Response.SuppressContent = true;
        HttpContext.Current.ApplicationInstance.CompleteRequest();
    }
}

Explanation: In ASP.NET, in a synchronous request handler, Server.Transfer and Response.Redirect (by way of internally calling Response.End) will call Thread.Abort (trigger the ThreadAbortException), do a blocking call by doing a synchronous flush of the request buffer to the client's browser. Then call ApplicationInstance.CompleteRequest() which skips further events and pipeline modules.

So in the case of an async handler, CompleteRequest() is called internally instead of Response.End, the ThreadAbortException is not thrown and code continues to execute after Transfer or Redirect is called. My click event handler for the page is an async void handler so the compiler makes it async and we get the async Server.Transfer Response.Redirect issues downstream.

Solution: Server.Execute runs the code of the page I want to transfer too and populates the response buffer. Flush flushes the buffer to the browser. Would prefer FlushAsync, avoiding the synchronous flush, but the containing method is not async. Abort throws the ThreadAbortException and bypasses the code after it. Response.Redirect is similar. Set the endReponse parameter to false so it doesn't call Response.End internally. Then Abort the thread to skip subsequent code after it in the stack.

In the click event handler I squash the ThreadAbortException in the exception handler. ResetAbort prevents the exception from being automatically re-thrown after the handler. Without ResetAbort, code was executing in the subsequent events. SuppressContent prevents both the current and transfer pages content from being displayed to the user at the same time. This happened in some situations depending on where the async code was. CompleteRequest skips events and http modules and jumps ahead to the EndRequest event.

I did attempt to use PageAsyncTask and related features on the async portion to avoid the async void click handler, but it only kicks off the task. It doesn't actually get awaited so it ends up executing code past where I need the value returned by it before it completes. Putting the whole event handler code in PageAsyncTask also didn't work since I need it to execute in the event handler and not later. ExecuteRegisteredAsyncTasks only works on the legacy asp.net synchronization context. Doesn't work with AspNetSynchronizationContext. Atleast not in .NET 4.6.1. Converting to .NET Core is not an option.

Not what I was hoping for in a solution. Hopefully someone more knowledgeable than I has a better one. That doesn't involve me recording half the existing program.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM