Server.Transfer,Response.Redirect和ApplicationInstance.CompleteRequest在ASP.Net中解决

问题描述 投票:0回答:1

我正在尝试向现有的asp.net表单应用程序添加异步功能。但我遇到了页面传输/重定向问题。大多数现有代码中的原因重定向/传输是从实用程序类或自定义控件等进行的。此外,在重定向/传输之后存在的代码在需要避免的堆栈中进一步存在。原始代码利用重定向/传输内部调用Response.End并通过ThreadAbortException短路线程的代码执行。

对于Server.Transfer和Response.Redirect,通过添加Async =“true”而异步的页面会使ThreadAbortException冒泡,浏览器最终会出现错误响应。我假设由于页面是异步的,当前页面请求在传输页面之前完成,而不是线程中止的同步方式,然后将开始处理传输页面。

当我使用Response.Redirect(“[somepagename]”,false)或Server.Execute时,它们会阻止当前响应结束,因此我在它们之后添加ApplicationInstance.CompleteRequest,这会跳过以后的事件和处理程序管道处理。但是重定向/执行后的代码会继续,这会导致其他错误/问题。 (例如,再次调用Response.Redirect,根据ThreadAbortException最初跳过的逻辑转到另一个页面。)

我还尝试了Server.Execute和ApplicationInstance.CompleteRequest之间的Response.FlushAsync,它对浏览器来说没问题。由于FlushAsync从传输页面发送缓冲响应,因此CompleteRequest之后的异常不会显示给浏览器(至少在dev中)。但是,如果第一页的刷新或继续代码执行首先完成,那仍然可能存在竞争条件。并且仍然执行CompleteRequest之后的后续代码,这可能有副作用。

原始代码:

public static void GoToPage( string page, bool transfer )
{
    if ( transfer ){HttpContext.Current.Server.Transfer( page, false ); }
    else { HttpContext.Current.Response.Redirect( page ); }
}
c# asp.net async-await response.redirect server.transfer
1个回答
0
投票

我有一个解决方案。我将调用的Server.Transfer部分更改为:

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 );
    }
}

然后更新了异步单击偶数处理程序以捕获ThreadAbortException。异常处理程序调用ResetAbort和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();
    }
}

说明:在ASP.NET中,在同步请求处理程序中,Server.Transfer和Response.Redirect(通过内部调用Response.End)将调用Thread.Abort(触发ThreadAbortException),通过执​​行同步刷新来执行阻塞调用请求缓冲区到客户端的浏览器。然后调用ApplicationInstance.CompleteRequest(),它会跳过更多事件和管道模块。

因此,对于异步处理程序,在内部调用CompleteRequest()而不是Response.End,不会抛出ThreadAbortException,并且在调用Transfer或Redirect之后代码将继续执行。我的页面的click事件处理程序是一个异步void处理程序,因此编译器使它成为异步,我们得到async Server.Transfer Response.Redirect向下游发出。

解决方案:Server.Execute也运行我想要传输的页面的代码并填充响应缓冲区。刷新将缓冲区刷新到浏览器。更喜欢FlushAsync,避免同步刷新,但包含方法不是异步。 Abort抛出ThreadAbortException并绕过它之后的代码。 Response.Redirect类似。将endReponse参数设置为false,因此它不会在内部调用Response.End。然后中止线程以跳过堆栈中的后续代码。

在click事件处理程序中,我在异常处理程序中压缩ThreadAbortException。 ResetAbort可防止在处理程序之后自动重新抛出异常。没有ResetAbort,代码在后续事件中执行。 SuppressContent可防止当前页面内容和传输页面内容同时显示给用户。在某些情况下会发生这种情况,具体取决于异步代码的位置。 CompleteRequest会跳过事件和http模块,并跳转到EndRequest事件。

我确实尝试在异步部分使用PageAsyncTask和相关功能来避免异步void click处理程序,但它只启动任务。它实际上并没有等待,所以它最终会执行代码,直到我需要它完成之前返回的值。将整个事件处理程序代码放在PageAsyncTask中也没有用,因为我需要它在事件处理程序中执行而不是以后执行。 ExecuteRegisteredAsyncTasks仅适用于旧版asp.net同步上下文。不适用于AspNetSynchronizationContext。至少不在.NET 4.6.1中。转换为.NET Core不是一种选择。

不是我在解决方案中所希望的。希望比我知识渊博的人有更好的知识。这并不涉及我录制现有节目的一半。

© www.soinside.com 2019 - 2024. All rights reserved.