zoukankan      html  css  js  c++  java
  • 为啥 Response.Write 后,View就不渲染了?

    一:背景

    1. 讲故事

    前几天群里有一位朋友聊到,为什么我在 Action 中执行一句 Response.Write 之后,后续的 View 就不呈现了,如果脑子中没有画面,那就上测试代码:

    
        public class HomeController : Controller
        {
            public IActionResult Index()
            {
                Response.WriteAsync("hello world!");
                return View();
            }
        }
    
    

    结果还是挺有意思的,大家都知道,默认情况下会渲染 /Home/Index 对应的 view 页面,但这里被 Response.WriteAsync 插了一杠子,气的 view 都渲染不出来了,那接下来就来找一找 view 为啥这么生气?

    二:寻找真相

    1. 从 Logger 入手

    相信很多人都在用 aspnetcore 中的 logger 记录日志,为什么要首选这个 logger 呢?因为它在 web框架 中是一等公民的存在,毕竟底层源码各处都嵌入着这玩意哈,随便找点代码:

    
    internal abstract class ActionMethodExecutor
    {
        private Task ResultNext<TFilter, TFilterAsync>(ref ResourceInvoker.State next, ref ResourceInvoker.Scope scope, [Nullable(2)] ref object state, ref bool isCompleted) where TFilter : class, IResultFilter where TFilterAsync : class, IAsyncResultFilter
        {
        	ResourceInvoker.ResultExecutingContextSealed resultExecutingContext3 = this._resultExecutingContext;
    		this._diagnosticListener.BeforeOnResultExecuting(resultExecutingContext3, tfilter);
    		this._logger.BeforeExecutingMethodOnFilter(filterType, "OnResultExecuting", tfilter);
    		tfilter.OnResultExecuting(resultExecutingContext3);
    		this._diagnosticListener.AfterOnResultExecuting(resultExecutingContext3, tfilter);
    		this._logger.AfterExecutingMethodOnFilter(filterType, "OnResultExecuting", tfilter);
    		if (this._resultExecutingContext.Cancel)
    		{
    			this._logger.ResultFilterShortCircuited(tfilter);
    			this._resultExecutedContext = new ResourceInvoker.ResultExecutedContextSealed(resultExecutingContext3, this._filters, resultExecutingContext3.Result, this._instance)
    			{
    				Canceled = true
    			};
    			goto IL_39E;
    		}
        }
    }
    
    

    而且大家想想,这种写法特别奇葩,我想底层框架中的 logger 定会有所反馈,接下来在启动程序的时候采用 WebApplication1 的模式启动,如下图:

    启动后,在控制台上可以看到一堆报错信息:

    
    info: Microsoft.Hosting.Lifetime[0]
          Now listening on: http://localhost:5000
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Development
    info: Microsoft.Hosting.Lifetime[0]
          Content root path: E:
    et5WebApplication1WebApplication1
    fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
          An unhandled exception has occurred while executing the request.
    System.InvalidOperationException: Headers are read-only, response has already started.
       at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.ThrowHeadersReadOnlyException()
       at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpHeaders.Microsoft.AspNetCore.Http.IHeaderDictionary.set_Item(String key, StringValues value)
       at Microsoft.AspNetCore.Http.DefaultHttpResponse.set_ContentType(String value)
       at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
       at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
       at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
       at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeResultAsync>g__Logged|21_0(ResourceInvoker invoker, IActionResult result)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters()
    
    

    异常信息非常明显:Headers are read-only, response has already started,大概就是说,header是只读的,response已是启动状态了,从调用堆栈的 ViewExecutor.ExecuteAsync 处可看出,代码准备渲染 view,在 set_ContentType 处遭遇异常,结束了后续渲染流程。

    接下来一起看下,为什么会触发这个异常???

    三: 调试源码寻找异常的原因

    1. dnspy 调试

    除了从异常堆栈中找到最早的异常代码处,这里还说一个小技巧,使用 ndspy 的 异常断点功能,在异常设置面板 定位 InvalidOperationException 异常即可。

    接下来就可以让程序跑起来,当异常抛出时会自动断下来。

    仔细看一下图中的文字标注,还是很好理解的,接下来继续追一下: response.ContentType = contentType2; 内部都做了什么。

    
    		public override string ContentType
    		{
    			get
    			{
    				return this.Headers[HeaderNames.ContentType];
    			}
    			set
    			{
    				if (string.IsNullOrEmpty(value))
    				{
    					this.HttpResponseFeature.Headers.Remove(HeaderNames.ContentType);
    					return;
    				}
    				this.HttpResponseFeature.Headers[HeaderNames.ContentType] = value;
    			}
    		}
    
    

    可以看到 内部是给 this.HttpResponseFeature.Headers 赋值的,继续往下追:

    从图中可以看到,最后的 HttpHeader._isReadOnly =true 导致异常的发生,罪魁祸首哈,接下来研究下这句 HttpHeader._isReadOnly=true 是何时被赋值的。

    2. _isReadOnly=true 何时发生

    这个问题就简单多了,必定是 Response.WriteAsync("hello world!"); 造成了 _isReadOnly=true ,在 HttpHeader 下有一个 SetReadOnly 方法用于对 _isReadOnly 字段的封装,代码如下:

    
    internal abstract class HttpHeaders 
    {
        public void SetReadOnly()
        {
            this._isReadOnly = true;
        }
    }        
    
    

    接下来在该方法处下一个断点,继续调试,如下图:

    从图中可看到,原来 Response.WriteAsync("hello world!") 是可以封锁 HttpHeaders的,后续任何再对 HttpHeader 的操作都是无效的。。。

    其实大家也可以想一想,不同的response,肯定会有不同的 header,要想叠加的话这辈子都不可能的,只能让后面的报错,如下:

    
    1. response:
    
    HTTP/1.1 200 OK
    Date: Mon, 19 Oct 2020 14:37:54 GMT
    Server: Kestrel
    Transfer-Encoding: chunked
    
    c
    hello world!
    
    
    2. view:
    
    HTTP/1.1 200 OK
    Date: Mon, 19 Oct 2020 14:39:01 GMT
    Content-Type: text/html; charset=utf-8
    Server: Kestrel
    Content-Length: 2239
    
    

    四: 总结

    这篇就是对群聊天过程中抛出问题的个人探究,一家之言,不过挺有意思,大家也可以多用用调试工具寻找问题,证明问题,纸上得来终觉浅,绝知此事要躬行,好了,希望本篇对您有帮助!

    更多高质量干货:参见我的 GitHub: dotnetfly

    图片名称
  • 相关阅读:
    Git 的使用
    state介绍
    salt之pillar组件
    salt之grains组件
    python之commands模块
    install命令
    salt常用模块及API
    locate包的安装
    集中化管理平台Saltstack安装配置
    系统批量运维管理器pexpect的使用
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/13844539.html
Copyright © 2011-2022 走看看