前幾天羣裏有一位朋友聊到,爲何我在 Action 中執行一句 Response.Write
以後,後續的 View 就不呈現了,若是腦子中沒有畫面,那就上測試代碼:html
public class HomeController : Controller { public IActionResult Index() { Response.WriteAsync("hello world!"); return View(); } }
結果仍是挺有意思的,你們都知道,默認狀況下會渲染 /Home/Index
對應的 view 頁面,但這裏被 Response.WriteAsync
插了一槓子,氣的 view 都渲染不出來了,那接下來就來找一找 view 爲啥這麼生氣?git
相信不少人都在用 aspnetcore 中的 logger 記錄日誌,爲何要首選這個 logger 呢?由於它在 web框架 中是一等公民的存在,畢竟底層源碼各處都嵌入着這玩意哈,隨便找點代碼:github
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
的模式啓動,以下圖:web
啓動後,在控制檯上能夠看到一堆報錯信息:框架
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:\net5\WebApplication1\WebApplication1 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 處遭遇異常,結束了後續渲染流程。ide
接下來一塊兒看下,爲何會觸發這個異常???工具
除了從異常堆棧中找到最先的異常代碼處,這裏還說一個小技巧,使用 ndspy 的 異常斷點功能,在異常設置面板 定位 InvalidOperationException
異常便可。測試
接下來就可讓程序跑起來,當異常拋出時會自動斷下來。ui
仔細看一下圖中的文字標註,仍是很好理解的,接下來繼續追一下: response.ContentType = contentType2;
內部都作了什麼。this
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
是什麼時候被賦值的。
這個問題就簡單多了,一定是 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