【问题标题】:Server.Transfer, Response.Redirect, and ApplicationInstance.CompleteRequest work around in ASP.NetServer.Transfer、Response.Redirect 和 ApplicationInstance.CompleteRequest 在 ASP.Net 中工作
【发布时间】:2019-07-30 00:41:35
【问题描述】:

我正在尝试将异步功能添加到现有的 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,这在浏览器中看起来不错。 CompleteRequest 之后的异常不会显示给浏览器(至少在开发中),因为 FlushAsync 从传输页面发送缓冲响应。但是,如果第一页的刷新或继续代码执行首先完成,那仍然可能存在竞争条件。 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】:

我有一个解决方案。我将调用的 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 后代码继续执行。我的页面点击事件处理程序是一个异步 void 处理程序,因此编译器使其异步,我们在下游得到异步 Server.Transfer Response.Redirect 问题。

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-19
    • 2011-10-10
    • 2014-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-19
    • 2010-12-17
    相关资源
    最近更新 更多