为啥 Response.Write 后,View就不渲染了?

为啥 Response.Write 后,View就不渲染了?,第1张

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

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

@H_404_9@    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框架 中是一等公民的存在,毕竟底层源码各处都嵌入着这玩意哈,随便找点代码:

@H_404_9@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,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 的模式启动,如下图:

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

@H_404_9@info: Microsoft.Hosting.lifetime[0]      Now Listening on: http://localhost:5000info: Microsoft.Hosting.lifetime[0]      Application started. Press Ctrl+C to shut down.info: Microsoft.Hosting.lifetime[0]      Hosting environment: Developmentinfo: Microsoft.Hosting.lifetime[0]      Content root path: E:\net5\WebApplication1\WebApplication1fail: 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,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; 内部都做了什么。

@H_404_9@		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 字段的封装,代码如下:

@H_404_9@internal abstract class httpheaders {    public voID SetReadonly()    {        this._isReadonly = true;    }}        

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

从图中可看到,原来 Response.WriteAsync("Hello World!") 是可以封锁 httpheaders的,后续任何再对 httpheader 的 *** 作都是无效的。。。

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

@H_404_9@1. response:http/1.1 200 OKDate: Mon,19 Oct 2020 14:37:54 GMTServer: Kestreltransfer-encoding: chunkedcHello World!2. vIEw:http/1.1 200 OKDate: Mon,19 Oct 2020 14:39:01 GMTContent-Type: text/HTML; charset=utf-8Server: KestrelContent-Length: 2239
四: 总结

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

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

总结

以上是内存溢出为你收集整理的为啥 Response.Write 后,View就不渲染了?全部内容,希望文章能够帮你解决为啥 Response.Write 后,View就不渲染了?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1213790.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-04
下一篇 2022-06-04

发表评论

登录后才能评论

评论列表(0条)

保存