ASP.NET Core 2.2 推出已经有一段时间了,其中有个新功能,能够使用新的AspNetCoreModuleV2并且在IIS上使用InProcess模式部署,以大幅提高性能。这几天Azure App Service终于完成了这个新版模块的部署,我第一时间将我的博客配置到新模块上,结果爆了。我们来看看原因和解决方式。
如果不知道什么是InProcess模式的话,简单来说,就是原先ASP.NET Core确实可以跑在IIS上,但其实是由一个名为AspNetCoreModule的IIS模块调用dotnet.exe启动kestrel来跑的,所以进程名字实际上是dotnet.exe。
而ASP.NET Core 2.2里新增了InProcess模式,可以在IIS自己的w3wp进程中跑你的应用。这个InProcess的In也就是In在了w3wp里的意思。据官网的描述,这种运行方式可以有400%的性能提升。
有兴趣可以看看官网的详细介绍:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/index?view=aspnetcore-2.2&tabs=windows#in-process-hosting-model
生产环境大爆炸
根据原先对ASP.NET Core的了解,我开发时候一般不会使用IIS去测试,用kestrel在开发环境测完以后直接使用Azure DevOps部署到Azure App Service上跑,结果今天升级到InProcess模式以后,生产环境爆了,而开发机的kestrel怎么弄都是好的。
启动失败,我的整个博客网站无法访问,好牛逼啊!
故障分析
还好微软智慧云Azure提供的全球独一无二的kudu工具可以非常方便的看到日志,日志显示:
[2018-12-26 12:06:26.5616][RD00155DB8C92A][Fatal][Microsoft.AspNetCore.Hosting.Internal.WebHost] Application startup exception System.IO.FileNotFoundException: Could not find file 'D:Windowssystem32urlrewrite.xml'.
File name: 'D:Windowssystem32urlrewrite.xml'
怎么会这样?我的代码访问的明明是应用根目录下的文件,为何跑到系统目录去了?再一看启动日志:
[2018-12-26 12:06:23.7946][RD00155DB8C92A][Info][Moonglade.Web.Program] Moonglade is starting, hail Microsoft!
--------------------------------------------------------
Version: 10.0.6934.1000
Directory: D:Windowssystem32
x64Process: True
OSVersion: Microsoft Windows 10.0.14393
AppDomain: Moonglade.Web
UserName: moonglade
--------------------------------------------------------
故障代码是这段:
using (var sr = File.OpenText("urlrewrite.xml"))
{
...
}
最终发现,在Kestrel下运行的时候,Environment.CurrentDirectory指向的是应用根目录,而在IIS的InProcess模式下运行的时候,则指向系统目录,最终导致应用里只要间接或直接使用Environment.CurrentDirectory的代码,都会爆。
解决办法
我们可以利用IHostingEnvironment接口里提供的ContentRootPath属性来获取当前应用目录的绝对路径,这是在Kestrel和IIS中行为一致的。我的故障代码正好位于可以访问到IHostingEnvironment的地方,因此做如下修改:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
string baseDir = env.ContentRootPath;
using (var sr = File.OpenText(Path.Combine(baseDir, "urlrewrite.xml")))
{
...
}
}
对于没法直接访问IHostingEnvironment又懒得做依赖注入的地方,可以这么玩:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
string baseDir = env.ContentRootPath;
...
AppDomain.CurrentDomain.SetData(Constants.AppBaseDirectory, baseDir);
}
然后在要用的地方GetData()就好了:
var configSource = $@"{AppDomain.CurrentDomain.GetData(Constants.AppBaseDirectory)}mailConfiguration.xml";
再次部署上线,问题成功解决!
其他注意事项
如果你在VS里使用IIS去debug,比如这样设置的话:
会产生一个debug配置的web.config文件,而这个文件在默认情况下会参与你CI/CD环境的编译和发布,最终导致你指定用release模式编译的网站,上线之后是debug标记的。一定记得要手工排除这个文件。
ASP.NET Core 2.2 以后,再也不能想当然地认为开发ASP.NET Core 用不用IIS都无所谓了,真的是有些东西在IIS和Kestrel下行为不一致,因此推荐大家如果生产环境在使用IIS的话,上线之前务必在本地的IIS上用同样配置测试一遍。