记一次Razor Pages无法编译问题及解决

解决方案写在前面:更新Visual Studio及相关组件,本人版本自17.8.0更新至17.11.4

缘起于公司的一个业务接口,在有一些信息需要在应用内嵌的webview中展示,信息不少,涉及的前端技术不复杂,但是拼字符串太罗嗦,所以想到了添加一个Razor页面,所以,常规逻辑,在服务上注册'''AddRazorPages''',构建后使用映射方法'''MapRazorPages''',然后调试。这次调试没有问题,页面也能正常显示。因为是微信的小程序,并且是在手机上演示,网站地址要在配置的业务域名上,所以基本写完了页面,第一时间就把应用部署到了iis的测试端口上。然后问题就发生了------------配置的首页返回404,访问业务所在的url也返回404。

因为几乎是第一次正式用Razor,所以尝试了检查url,补全index甚至补全.cshtml,都是一样的404。再后来检查了应用的日志,发现除了页面访问的w3cLog记录,没有任何其他的记录,但页面使用了Ef core 对SQl Server 的查询,而查询的日志项并没有在日志过滤规则之外,所以基本确定了页面并没有被执行。

本地调试可以执行,部署后不能执行,第一时间就是想到的环境不一致,Development和Production,对应开发环境和生产环境。在Visual Studio使用调试时ASPNETCORE_ENVIRONMENT这个变量要被设置成"Development"的,但直接执行,该环境变量没有定义,应用默认的状态是Production。

在Debug文件夹运行可执行文件,结果发现依然404。注意到Pages 文件夹没有,第一时间考虑有两种可能:一、项目设置错误,没有在生成时把文件复制到文件夹;二、考虑cshtml文件被编译到dll中了(实际上就是这样的,但当时的我并不能确认)。

先考虑第一种可能,这个实操不难,只需要在项目配置中添加

    <Content Update="Pages\*.cshtml">
      <CopyToOutputDirectory>Always</CopyToOutputDirectory>
    </Content>

再生成一次,第一遍不配置ASPNETCORE_ENVIRONMENT变量,结果是相关页面依旧404,第二遍,配置ASPNETCORE_ENVIRONMENT为Development,终于,页面正常展示了。但奇怪的是,进一步发布->运行时,依然需要将ASPNETCORE_ENVIRONMENT变量设置为Development??这不应该!(此时的我依然没觉得复制Pages/*.cshtml有什么问题,就是觉得配置被搞出问题了)设置为开发环境显然不合理,很多配置都是针对开发环境的,SwaggerUI也会展示,总不能在发布到服务器前再改一次代码吧?

不过,由于第二天要演示,而且由于微信小程序的各种**门槛,不得不部署,这时候IIS又闹情绪,照网上的办法,又是配置 ,又是配置 的,运行起来的服务始终是生产环境,总不能改系统的环境变量,毕竟上面的陈年老项目认到这个变量指不定激动的抽过去了也不一定。不过,办法总比困难多,Environment.SetEnvironmentVariable方法解决这个问题,其实也是一开始就想到它了,但为了不动现有的代码,一直没舍得用,遵循下老祖宗的开闭原则嘛。但第二天的演示更要紧,不得不做一个违背老祖宗的决定~

演示结束,立马回到这个问题,首先想到的就是新建一个项目。这下可不得了了,不论改环境变量还是复制文件,都成了404了。明明就是默认的Razor Pages模板,去MSDN上给的代码也都一样,但就是跑不起来。无头苍蝇一样搜了各种办法,但都没有针对我的问题,寻找文心一言和chatGPT的解答,也都是不合我意。

不过,最终在各种关键词搜索中,找到了杨中科老师在百家号上的一篇回答《如何让asp.net core mvc发布时候不编译cshtml视图》,虽然和我的想法相反,但这总算是相关的了。文章中介绍了让cshtml文件在运行时编译的方法,感觉能找到点苗头,于是按他说的一试,页面不再是404了。

或许到这,也就该结束了,毕竟用这方法不在考虑修改环境变量,也不用修改其他代码,问题算是圆满的解决了。。。吗?当然可以不再进一步探索,但是这篇反方向的解决方案让我有了一个新的灵感------------:

文章满篇都在提要在运行时编译要做的额外配置,那要是不做呢?默认情况下它到底以何种方式运行。杨中科老师的文章中提到的两个属性标签已经让答案呼之欲出了:MvcRazorCompileOnPublish被置为了false,RazorCompileOnBuild同样被置为了false,也就是说,默认情况下,生成时和发布时Razor文件可能是被编译的。至于编译的形式,有可能就是jsp那样!(至于为什么先知道的jsp,学校给教的,大概在2020~2021年,哈哈:),现在估计依然在教。也是完成作业的过程中偶尔接触到的jsp文件编译后的反编译代码)

要探究编译后的文件,自然是少不了ILSpy,以往用它来分析过线上代码的问题,所以想到反编译就想到了它。对发布后的dll(没有使用运行时编译配置)进行分析,发现相关的命名空间下只有Model文件的编译,页面内容相关的代码一点没有。

但现在,在当前的环境下,我也是穷途末路了,新项目都这样,就算又有配置问题,找出来也不异于大海捞针,况且如此的绝望,在第一天下班后的尝试中就已经发生在了自己的电脑上。只有用本办法了------------虚拟机。在一台Windows10环境的虚拟机上,我又重新搭建了一套Visual Studio,创建了新的Razor模板,运行。但这次,成功了,用了各种姿势都是。我不知道该说是意料之中还是意料之外。但总算是有成功的样本给我研究了。

拿到虚拟机上发布后的dll,丢到ILSpy中,这次能清楚的看到被编译到dll当中的页面内容,也不出意外,整体形式和jsp差不多。

但为什么?这一遭让我知道了cshtml在不特殊设置的情况下确实要被编译到dll中去的,相应的,把cshtml复制到构建文件夹并无必要。我一开始考虑的配置问题似乎就不存在......

剩下的就是对项目无尽的蹂躏了。当我在一次次的生成、调试或是发布时,我注意到一条警告:

CS9057 分析器程序集"C:\Program Files\dotnet\sdk\8.0.201\Sdks\Microsoft.NET.Sdk.Razor\source-generators\Microsoft.CodeAnalysis.Razor.Compiler.SourceGenerators.dll"引用了编译器的版本"4.9.0.0",该版本高于当前正在运行的版本"4.8.0.0"

要放在平时,我根本不会在意,毕竟满屏的"属性未在构造器中赋值"中,它并不起眼,况且,就算偶然注意到它,也已经在多次偶然中与它相熟了。往常也没有奇怪的异常与它相关。但这次显眼Compiler和SourceGenerators让我无法再忽视它的存在。cshtml不就是在编译时没有生成成相应的代码嘛。

在前面蹂躏的过程中,也试了使用dotnet publish,结果是成功的,换了不同的sdk版本,都是成功。但自从注意到这条警告,我就试图在dotnet 的生成和发布中找到它,始终无果。

VS编译器版本不兼容了?怎么更新一下?vs编译用的不是我安装的sdk?

此时,自然就想到了更新Visual Studio,找到 vs installer 发现确实有新版本,发现版本号差的并不是很大,所以一开始并没有报太大希望,但更新后再编译,一切问题都消失了......

困扰我这么久的问题,竟然一次更新就解决了,完全就没有幻想中力挽狂澜的澎湃啊啊啊啊啊~~~