java谁对view进行渲染_为啥 Response.Write 后,View就不渲染了?

一:背景

1. 讲故事

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

{

public IActionResult Index()

{

Response.WriteAsync("hello world!");

return View();

}

}

2d2280334ae8a6c4dcf0865e707908be.png

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

二:寻找真相

1. 从 Logger 入手

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

{

private Task ResultNext(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 的模式启动,如下图:

2e3508c408989c16da990b26c01a620d.png

启动后,在控制台上可以看到一堆报错信息: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.g__Logged|21_0(ResourceInvoker invoker, IActionResult result)

at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.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 异常即可。

ba2a6fd7b7cd3963aae30aebb41529f9.png

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

1871118ad8dd160268fcb2a4ade34091.png

仔细看一下图中的文字标注,还是很好理解的,接下来继续追一下: 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 赋值的,继续往下追:

95d1f54e8fd6cc506575ab2b6c250689.png

从图中可以看到,最后的 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;

}

}

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

1fd0698bdacb997339ff63117590fcec.png

从图中可看到,原来 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

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[为啥 Response.Write 后,View就不渲染了?]http://www.zyiz.net/tech/detail-148195.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
private void initData() { //1.创建一个请求队列 RequestQueue requestQueue=Volley.newRequestQueue(MainActivity.this); //2.创建一个请求 String URL=BASE_URL; JsonObjectRequest jsonObjectRequest=new JsonObjectRequest(URL, new Response.Listener<JSONObject>() { @Override public void onResponse(JSONObject response) { initFoods(response.toString()); } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // Log.d(TAG,"请求失败"+error); } }); //3.将创建好的请求添加到请求队列中 requestQueue.add(jsonObjectRequest); foodListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) { view =View.inflate(MainActivity.this,R.layout.food_dialog,null); Food item = foods.get(i); food_edit_cancel =view.findViewById(R.id.food_edit_cancel); food_edit_cal = view.findViewById(R.id.food_edit_cal); food_edit_des =view.findViewById(R.id.food_edit_des); food_edit_doWay= view.findViewById(R.id.food_edit_doWay); food_edit_name =view.findViewById(R.id.food_edit_name); food_edit_type =view.findViewById(R.id.food_edit_type); food_edit_cal.setText(String.valueOf(item.getCal())); food_edit_des.setText(item.getDes()); food_edit_doWay.setText(item.getDoway()); food_edit_name.setText(item.getFoodname()); food_edit_type.setText(item.getFoodtype()); AlertDialog.Builder builder = new FoodDialog.Builder(MainActivity.this); final AlertDialog alertDialog = builder.create(); alertDialog.setView(view); food_edit_cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { alertDialog.cancel(); } }); alertDialog.show(); } }); }
最新发布
06-12

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值