StackExchange / StackExchange.Precompilation

Roslyn based csc.exe and aspnet_compiler.exe replacement with metaprogramming hooks for ASP.NET MVC projects from the pre-DNX era
MIT License
155 stars 37 forks source link

Still seeing runtime compilation when using StackExchange.Precompilation.Build #38

Open mike-riedel opened 6 years ago

mike-riedel commented 6 years ago

I believe I've followed the necessary steps to use precompiled .cshtml views but when I run my web app and click on various pages, I still see significant delays and see VBCSCompiler.exe appearing in Task Manager, leading me to believe that the precompiled views are not being used.

Do I have the wrong expectations about what this package does or am I doing something wrong?

I followed these steps, per the instructions on github:

  1. Added the StackExchange.Precompilation.Build and StackExchange.Precompilation nugets to web project which uses Razor and .cshtml files
  2. Added true to .csproj
  3. Cleared existing ViewEngines and added new instance of PrecompiledViewEngine followed by RoslynRazorViewEngine

In the build output I've verified StackExchange.Precompiler.exe is being run, and while debugging I've verified PrecompiledViewEngine is finding what seem to be precompiled views in the project's assembly. And yet, I still see long delays and VBCSCompiler being run whenever new cshtml views are accessed.

m0sa commented 6 years ago

You can get more details on what is happening by hooking into the following events:

StackExchange.Precompilation.RoslynRazorViewEngine.CompilingPath += OnRuntimeCompilation;
System.Web.WebPages.Razor.RazorBuildProvider.CompilingPath += OnRuntimeCompilation;

... and have the handler log it somewhere:

private static void OnRuntimeCompilation(object sender, System.Web.WebPages.Razor.CompilingPathEventArgs e)
{
    // TODO log details somewhere
}

There are also other thing asp.net tries to build at runtime, like global.asax, helpers, etc.

There is a workaround for global.asax in the test project via the PreApplicationStartMethodAttribute and the correct handler.

If you're sure you're not using anything else, you can also add a PrecompiledApp.config to you web application project with the following content:

<?xml version="1.0"?>
<precompiledApp version="42" updatable="true"/>

It tells the BuildManager not to attempt building any special-purpose folders such as App_Code, App_GlobalResources, and App_WebReferences, and special files such as Global.asax.

mike-riedel commented 6 years ago

Thanks for the helpful information and suggestions.

I added trace output for those events, applied the global.asax workaround and create the PrecompiledApp.config file. The traces show a runtime compilation event whenever a "new" .cshtml page is requested. In other words, if the same page is requested multiple times, the event only occurs for the first request. The sender of the events is always System.Web.WebPages.Razor.RazorBuildProvider.

m0sa commented 6 years ago

You can get a list of all precompiled views the precompiled view engine knows about via the PrecompiledViewEngine.ViewPaths property. If they aren't there, you didn't initialize it correctly. If they are, plase elaborate, as something might be broken in the path resolution / normalization.

mike-riedel commented 6 years ago

I have two web projects in the same solution, one for sign-in and the other for whatever. The sign-in app never fires a runtime compilation event when its pages are requested. But the admin app wants to runtime-compile every page other than "~/index.cshtml". While debugging, PrecompiledViewEngine.ViewPaths contains reasonable paths which look like they should correlate with a particular Request.Url and in one case it seems to work and in the other it does not.

For example, ViewPaths contains "~/app/services/services.cshtml" and a request comes in with RawUrl = "/rightfaxsdk/admin/app/services/services.cshtml" and AppRelativeCurrentExecutionFilePath = "~/app/services/services.cshtml", which is an exact match for the entry in ViewPaths. But the CompilingPath event is fired, which to me means the page is being compiled.

I've spent hours looking for relevant differences between the two apps, including detailed file diffs, but haven't found anything that seems important. The sign-in app has a lot less pages (10 instead of 89) but I can't imagine that would matter.

mike-riedel commented 6 years ago

We use HttpContext.RewritePath for most incoming requests (primarily to remove cache-busting fingerprints). Could the resulting changes to the Request cause the caching not to locate the correct precompiled file?

m0sa commented 6 years ago

Maybe, if you use partial view names (e.g. View("Foo"), or just View() inside the Foo method). Have you tried specifying the full view path instead (e.g. View("~/Views/Bar/Foo.cshtml"))?

michael-huxtable commented 6 years ago

Adding to this, I found that using the package with VS2015, I consistently get this error message:

3>CSC : warning : Couldn't load reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' from 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2\Facades\System.Runtime.dll' - 'Could not load file or assembly 'System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. An operation is not legal in the current state. (Exception from HRESULT: 0x80131509)'
3>CSC : error : An unhandled exception occured
3>CSC : error : System.IO.FileNotFoundException: Could not load file or assembly 'System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified.

Using VS2017, we do not see these build errors. Following the guide to copy the DLLs into the tools directory results in a different error when using VS2015:

3>CSC : warning : Couldn't load reference 'System.Runtime, Version=4.0.20.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' from 'C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.2\Facades\System.Runtime.dll' - 'Could not load file or assembly 'System.Runtime, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. An operation is not legal in the current state. (Exception from HRESULT: 0x80131509)'
3>C:\Program Files (x86)\MSBuild\14.0\bin\Microsoft.CSharp.Core.targets(67,5): error MSB6006: "StackExchange.Precompiler.exe" exited with code -1073741819.

For now I have migrated to VS2017, Is this likely to be due to the MSBuild version used? Or related to dependency resolution?