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.
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..
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();
}