ASP.NET MVC 3: 服务器发送HTTP头之后无法追加头信息

5
我们正在忙于将一个使用3.5框架的ASP.NET MVC 2应用程序升级为在4.0框架上运行的ASP.NET MVC 3应用程序。
有一个页面,当使用浏览器返回按钮时会抛出异常。为了支持该页面的浏览器返回按钮,我们实现了一个系统,在返回该页面时重新请求该页面的结果。然而,我没有明确的迹象可以查找问题,因为我总是只能找到错误。
Server cannot append header after HTTP headers have been sent.

带有堆栈跟踪
at System.Web.HttpResponse.AppendHeader(String name, String value)
at System.Web.HttpResponseWrapper.AppendHeader(String name, String value)
at System.Web.Mvc.MvcHandler.AddVersionHeader(HttpContextBase httpContext)
at System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory)
at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<BeginProcessRequest>b__2()
at System.Web.Mvc.SecurityUtil.<>c__DisplayClassb`1.<ProcessInApplicationTrust>b__a()
at System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f)
at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action)
at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust[TResult](Func`1 func)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state)
at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

HTTP头已经被发送的原因是什么?

提前感谢, IvanL

编辑: 在解决此问题时,我获得了新的信息和见解。其中一个答案提到的异步控制器让我想起了一些事情。当我发现我必须更改以下内容才能使旧的MVC2方法运行时:

[HttpPost, ValidateInput(false)]
public void SearchResultOverview(SearchResultViewModel model, string searchUrl)
{
    if (!string.IsNullOrEmpty(searchUrl))
    {
        searchUrl = searchUrl.Replace("SearchPartial", "SearchPartialInternal");

        //NOTE MVC 3
        HttpContext.Server.TransferRequest(searchUrl, true);

        //NOTE MVC 2
        //System.Web.HttpContext.Current.RewritePath(searchUrl, false);

        //IHttpHandler httpHandler = new MvcHttpHandler();
        //// Process request
        //httpHandler.ProcessRequest(System.Web.HttpContext.Current);
    }
}

当我查找TransferRequest方法时,我发现它“执行指定URL的异步操作并保留查询字符串参数。”(http://msdn.microsoft.com/en-us/library/system.web.httpserverutility.transferrequest.aspx
此外,在我发布的异常之前,还会抛出一个异常(由于我晚了捕获异常,所以我忽略了它)。这个异常是:
The SessionStateTempDataProvider class requires session state to be enabled.
   at System.Web.Mvc.SessionStateTempDataProvider.SaveTempData(ControllerContext controllerContext, IDictionary`2 values)
   at System.Web.Mvc.TempDataDictionary.Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider)
   at System.Web.Mvc.Controller.PossiblySaveTempData()
   at System.Web.Mvc.Controller.ExecuteCore()
   at System.Web.Mvc.ControllerBase.Execute(RequestContext requestContext)
   at System.Web.Mvc.ControllerBase.System.Web.Mvc.IController.Execute(RequestContext requestContext)
   at System.Web.Mvc.MvcHandler.<>c__DisplayClass6.<>c__DisplayClassb.<BeginProcessRequest>b__5()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass1.<MakeVoidDelegate>b__0()
   at System.Web.Mvc.Async.AsyncResultWrapper.<>c__DisplayClass8`1.<BeginSynchronous>b__7(IAsyncResult _)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.MvcHandler.<>c__DisplayClasse.<EndProcessRequest>b__d()
   at System.Web.Mvc.SecurityUtil.<GetCallInAppTrustThunk>b__0(Action f)
   at System.Web.Mvc.SecurityUtil.ProcessInApplicationTrust(Action action)
   at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult)
   at System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result)
   at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
   at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)

那么我该怎么让它工作?

3
好的,让我看看是否可以通过心灵感应推断出来……嗯……不行……你需要提供一些代码。你添加了哪些头文件?何时添加的? - Erik Funkenbusch
1
也许这会有所帮助:https://dev59.com/VHE95IYBdhLWcg3wRLwt - Erik Funkenbusch
1
@Mystere Man:这就是问题所在。我没有添加任何标题,似乎从MVC2到MVC3的更改引起了这个问题,因为在MVC2中代码可以正常运行而没有任何异常。如果你查看堆栈跟踪,你会注意到: at System.Web.Mvc.MvcHandler.AddVersionHeader(HttpContextBase httpContext),这意味着MVC本身正在尝试添加一个标题,但它不能。我尝试了你给我链接问题的解决方案response.BufferOutput = true,但它不起作用。我总是收到这个异常和堆栈跟踪。 - IvanL
你为什么要在一个动作方法中创建一个新的HttpHandler? - Andrew Barber
@Andrew Barber:这是为了处理较大的URL而完成的。字符串searchUrl是实际的URL,但它太大了,以至于一些浏览器拒绝使用它。为了规避这个问题,URL实际上被发布并在内部处理。在MVC 2中,这是通过启动一个新的请求处理程序并让它处理实际的URL来完成的,自从MVC 3以来,这已经不再可能了,我找到的解决方案是使用Server.TransferRequest()。然而,当浏览器返回按钮时,这种方法似乎不起作用...(当页面定期加载时,它完美地工作) - IvanL
3个回答

3
好消息,今天在更深入地研究异常原因后,我解决了自己的问题。帮助我理解异常和错误原因的第一个链接是:http://www.eggheadcafe.com/tutorials/asp-net/79c73563-408a-493e-a369-d4b380bce549/aspnet-using-servertransferrequest.aspx。它详细介绍了Server.TransferRequest的工作原理,并提到了非常重要的警告:在转移到子请求之前,必须通过主请求释放会话。深入了解如何在MVC中实现这一点后,我在stack overflow上找到了以下帖子:How to simulate Server.Transfer in ASP.NET MVC?
这篇文章又指向了一个非常重要的事情需要知道:throw new ApplicationException("TempData won't work with Server.TransferRequest!");所以我创建了TransferResult类,在需要的操作中通过该类返回。我发现现在特定情况下会触发此异常。我自己从未使用过TempData,但显然我的一个同事使用了。
由于 TempData 中包含的数据不重要,所以我决定在任何 Server.TransferRequest() 之前清除 TempData,这使得我的异常和问题消失了。
我要感谢所有帮助解决此问题的人,并很高兴能提供一个结论和解决方案,以便那些正在研究同样问题的人受益。
诚挚地,IvanL

你知道为什么在调用TransferRequest之前需要检查TempData中的数据吗? - yurart
1
如上所述,必须释放会话状态,而TempData似乎会“锁定”会话,使您无法传输请求。 - IvanL

2
如果页面上的缓冲已经关闭,就会出现这种情况。缓冲意味着 asp.net 在发送响应之前等待整个请求完成。这意味着头可以随时更改。当缓冲关闭时,输出将随生成而发送到客户端。因此,您不能随意更改标题,因为它们已经被发送。
从您的堆栈跟踪中,似乎是异步控制器,我想知道这是否与此有关。不过,我只是从您发布的内容猜测。
更新
更正一下,异步提及实际上是框架代码,与您的代码无关。但是,从您上面的代码来看,SearchResultOverview 是否是控制器上的操作?如果是,则使用您正在使用的方法传输执行是导致问题的原因。
它会导致 2 个 mvchandler 执行并相互干扰。路由是重定向请求的更好方式。

我将参考我在回复Mystere Man时写的评论。我尝试在BeginRequest中启用缓冲,但没有改变任何东西。 MVC2和MVC3之间有什么变化,突然之间头部被提前发送,我需要开始改变像缓冲这样的行为?你说它似乎是一个异步控制器,但据我所知,这个概念在MVC2中并不存在,这纯粹是一个从MVC2升级到MVC3的项目,使用了由ASP.NET团队编写的代码库更新工具。 - IvanL
确实,我对异步操作的理解有误。MVC 3通过它来处理所有事情。这是一个框架问题,而不是你的代码问题。 - Simon Halsey

0

我记得有一段时间我遇到过这个问题。我不记得确切的条件,但它与在自定义操作过滤器中直接设置标头和HTTP状态代码有关。

当时我的目标是在用户身份验证超时并单击ajax操作链接时显示自定义页面/消息(例如当他将页面保持打开一段时间然后回来并单击链接时),因此asp.net mvc没有在div内显示默认登录页面(有点丑)。我现在手头没有代码,但大致如下:

public class AjaxFilterAttribute : ActionFilterAttribute  
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAjaxRequest())
        {
            filterContext.HttpContext.Response.AddHeader("name", "value");
            filterContext.HttpContext.Response.StatusCode = 200;
            filterContext.Result = something;
        }

        base.OnActionExecuting(filterContext);
    }
}

事实是,在早期版本的asp.net mvc上尝试此代码会导致“无法追加标头”错误。我不记得如何解决它,但绝非易事。如果您认为该情况适用于您,我可以在我的旧项目中搜索已修复的代码。

希望能有所帮助。


能否告诉我为什么要踩这个问题?也许这并不完全适用于提问者的具体问题,但它可以帮助其他人。唉... - Francisco
我遇到了相同的问题。唯一的区别是我的逻辑在OnActionExecuted中。您介意分享修复方法吗? - sandesh kota
我没有它,它已经失落在时间中了...但关键部分是直接设置结果,就像这样:filterContext.Result = new HttpUnauthorizedResult(); - Francisco
下投票可能是因为你的回答不是一个真正的回答。“我有这个问题,但我不记得如何解决它”。什么鬼。 - jazzcat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接