Search code examples
asp.netasp.net-mvcazureazure-web-roles

Azure precompilation doesn't seem to work


I try to precompile my Azure WebApplication, so the first hit doesn't take multiple seconds for every first hit of a page.

I have the following code in my WebRole.Run():

using ( var server_manager = new ServerManager() )
{
    var main_site               = server_manager.Sites[RoleEnvironment.CurrentRoleInstance.Id + "_Web"];
    var main_application        = main_site.Applications["/"];
    var main_application_pool   = server_manager.ApplicationPools[main_application.ApplicationPoolName];

    string physical_path = main_application.VirtualDirectories["/"].PhysicalPath;

    main_application["preloadEnabled"] = true;
    main_application_pool["autoStart"] = true;
    main_application_pool["startMode"] = "AlwaysRunning";

    server_manager.CommitChanges();

    Log.Info( "Building Razor Pages", "WebRole" );

    var build_manager = new ClientBuildManager( "/", physical_path );
    build_manager.PrecompileApplication();

    Log.Info( "Building Razor Pages: Done", "WebRole" );
}

It doesn't seem to throw any Exceptions and when I look into the log it takes about 55 seconds to do build_manager.PrecompileApplication().

Seems about correct to me.

Except that when I try to load the pages the first time it still takes a hit. If I look into the MiniProfiler I see specificly that the Find part takes long. I still suspect it is the compilation, because 1.5 seconds just to find a file seems a bit long to me.

enter image description here

Is there anything wrong with my approach above? Is there a way to check if the page is really compiled? And in the case it is compiled, what else could it be? And why o why is stuff so complicated..


Solution

  • I never got it too work. Probably some weird Web.config / Azure / project setting that seems to be off, but I never found it.

    Before you read further I recommend trying this approach first for your project: https://stackoverflow.com/a/15902360/647845. (because it's not dependent on a hack)

    My workaround is relatively straightforward, I search for all Razor Views in the application, and than I just try to render them with a mocked ControllerContext, dismissing the results. Allmost all Views will throw an Exception, because of a nonvalid ViewModel, but that doesn't matter as long as the view got compiled in the first place.

    private void PrecompileViews()
    {
        string search_path = GetSearchPath();
    
        // Create a dummy ControllerContext - using the HomeController, but could be any controller
        var http_context = new HttpContext( new HttpRequest("", "http://dummy", ""), new HttpResponse(new StringWriter()) );
        http_context.Request.RequestContext.RouteData.Values.Add( "controller", "Home" );
        var controller_context = new ControllerContext( new HttpContextWrapper(http_context), http_context.Request.RequestContext.RouteData, new HomeController() );
    
        // loop through all views, and try to render them with the dummy ControllerContext
        foreach ( var file in Directory.GetFiles(search_path, "*.cshtml", SearchOption.AllDirectories) )
        {
            string relative_view_path = "~" + file.Replace( search_path, "", StringComparison.InvariantCultureIgnoreCase );
    
            var view_result = new ViewResult { ViewName = relative_view_path };
    
            try
            {
                view_result.ExecuteResult( controller_context );
            }
            catch (Exception)
            {
                // Almost all views will throw exceptions, because of a non valid ViewModel, 
                // but that doesn't matter because the view stills got compiled
            }
        }
    }
    

    GetSearchPath returns the root directory of your application:

    private string GetSearchPath()
    {
        string bin_path = Path.GetDirectoryName( Assembly.GetExecutingAssembly().GetName().CodeBase );
        string local_path = new Uri(bin_path).LocalPath;
        return Path.GetFullPath( Path.Combine( local_path, ".." ) );
    }
    

    And I call it all at the end of my Global.asax:

    protected void Application_Start()
    {
         [...]
         PrecompileViews();
     }