mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Improve response time for first time users
On the first time installation of Orchard, the user gets to the setup screen, which will take a while to finish (user entering data and the setup process itself). We use this opportunity to start a background task to "pre-compile" all the known views in the app folder, so that the application is more reponsive when the user hits the homepage and admin screens for the first time. --HG-- branch : dev
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Web.Mvc;
|
using System.Web.Mvc;
|
||||||
|
using Orchard.Environment;
|
||||||
using Orchard.FileSystems.AppData;
|
using Orchard.FileSystems.AppData;
|
||||||
using Orchard.Setup.Services;
|
using Orchard.Setup.Services;
|
||||||
using Orchard.Setup.ViewModels;
|
using Orchard.Setup.ViewModels;
|
||||||
@@ -11,11 +12,13 @@ namespace Orchard.Setup.Controllers {
|
|||||||
[ValidateInput(false), Themed]
|
[ValidateInput(false), Themed]
|
||||||
public class SetupController : Controller {
|
public class SetupController : Controller {
|
||||||
private readonly IAppDataFolder _appDataFolder;
|
private readonly IAppDataFolder _appDataFolder;
|
||||||
|
private readonly IViewsBackgroundCompilation _viewsBackgroundCompilation;
|
||||||
private readonly INotifier _notifier;
|
private readonly INotifier _notifier;
|
||||||
private readonly ISetupService _setupService;
|
private readonly ISetupService _setupService;
|
||||||
|
|
||||||
public SetupController(INotifier notifier, ISetupService setupService, IAppDataFolder appDataFolder) {
|
public SetupController(INotifier notifier, ISetupService setupService, IAppDataFolder appDataFolder, IViewsBackgroundCompilation viewsBackgroundCompilation) {
|
||||||
_appDataFolder = appDataFolder;
|
_appDataFolder = appDataFolder;
|
||||||
|
_viewsBackgroundCompilation = viewsBackgroundCompilation;
|
||||||
_notifier = notifier;
|
_notifier = notifier;
|
||||||
_setupService = setupService;
|
_setupService = setupService;
|
||||||
T = NullLocalizer.Instance;
|
T = NullLocalizer.Instance;
|
||||||
@@ -37,6 +40,17 @@ namespace Orchard.Setup.Controllers {
|
|||||||
|
|
||||||
public ActionResult Index() {
|
public ActionResult Index() {
|
||||||
var initialSettings = _setupService.Prime();
|
var initialSettings = _setupService.Prime();
|
||||||
|
|
||||||
|
// On the first time installation of Orchard, the user gets to the setup screen, which
|
||||||
|
// will take a while to finish (user inputting data and the setup process itself).
|
||||||
|
// We use this opportunity to start a background task to "pre-compile" all the known
|
||||||
|
// views in the app folder, so that the application is more reponsive when the user
|
||||||
|
// hits the homepage and admin screens for the first time.
|
||||||
|
if (StringComparer.OrdinalIgnoreCase.Equals(initialSettings.Name, "Default")) {
|
||||||
|
_viewsBackgroundCompilation.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
return IndexViewResult(new SetupViewModel { AdminUsername = "admin", DatabaseIsPreconfigured = !string.IsNullOrEmpty(initialSettings.DataProvider)});
|
return IndexViewResult(new SetupViewModel { AdminUsername = "admin", DatabaseIsPreconfigured = !string.IsNullOrEmpty(initialSettings.DataProvider)});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +89,11 @@ namespace Orchard.Setup.Controllers {
|
|||||||
|
|
||||||
_setupService.Setup(setupContext);
|
_setupService.Setup(setupContext);
|
||||||
|
|
||||||
|
// First time installation if finally done. Tell the background views compilation
|
||||||
|
// process to stop, so that it doesn't interfere with the user (asp.net compilation
|
||||||
|
// uses a "single lock" mechanism for compiling views).
|
||||||
|
_viewsBackgroundCompilation.Stop();
|
||||||
|
|
||||||
// redirect to the welcome page.
|
// redirect to the welcome page.
|
||||||
return Redirect("~/");
|
return Redirect("~/");
|
||||||
}
|
}
|
||||||
|
@@ -48,6 +48,7 @@ namespace Orchard.Environment {
|
|||||||
builder.RegisterType<DefaultProjectFileParser>().As<IProjectFileParser>().SingleInstance();
|
builder.RegisterType<DefaultProjectFileParser>().As<IProjectFileParser>().SingleInstance();
|
||||||
builder.RegisterType<DefaultAssemblyLoader>().As<IAssemblyLoader>().SingleInstance();
|
builder.RegisterType<DefaultAssemblyLoader>().As<IAssemblyLoader>().SingleInstance();
|
||||||
builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
|
builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
|
||||||
|
builder.RegisterType<ViewsBackgroundCompilation>().As<IViewsBackgroundCompilation>().SingleInstance();
|
||||||
|
|
||||||
RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder);
|
RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(builder);
|
||||||
RegisterVolatileProvider<AppDataFolder, IAppDataFolder>(builder);
|
RegisterVolatileProvider<AppDataFolder, IAppDataFolder>(builder);
|
||||||
|
155
src/Orchard/Environment/ViewsBackgroundCompilation.cs
Normal file
155
src/Orchard/Environment/ViewsBackgroundCompilation.cs
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Timers;
|
||||||
|
using System.Web.Compilation;
|
||||||
|
using Orchard.FileSystems.VirtualPath;
|
||||||
|
using Orchard.Logging;
|
||||||
|
|
||||||
|
namespace Orchard.Environment {
|
||||||
|
public interface IViewsBackgroundCompilation {
|
||||||
|
void Start();
|
||||||
|
void Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ViewsBackgroundCompilation : IViewsBackgroundCompilation {
|
||||||
|
private readonly IVirtualPathProvider _virtualPathProvider;
|
||||||
|
private volatile bool _stopping;
|
||||||
|
|
||||||
|
public ViewsBackgroundCompilation(IVirtualPathProvider virtualPathProvider) {
|
||||||
|
_virtualPathProvider = virtualPathProvider;
|
||||||
|
|
||||||
|
Logger = NullLogger.Instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger Logger { get; set; }
|
||||||
|
|
||||||
|
public void Start() {
|
||||||
|
_stopping = false;
|
||||||
|
var timer = new Timer();
|
||||||
|
timer.Elapsed += CompileViews;
|
||||||
|
timer.Interval = TimeSpan.FromMilliseconds(100).TotalMilliseconds;
|
||||||
|
timer.AutoReset = false;
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop() {
|
||||||
|
_stopping = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CompilationContext {
|
||||||
|
public IEnumerable<string> DirectoriesToBrowse { get; set; }
|
||||||
|
public IEnumerable<string> FileExtensionsToCompile { get; set; }
|
||||||
|
public HashSet<string> ProcessedDirectories { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompileViews(object sender, ElapsedEventArgs elapsedEventArgs) {
|
||||||
|
Logger.Information("Starting background compilation of views");
|
||||||
|
((Timer)sender).Stop();
|
||||||
|
|
||||||
|
// Hard-coded context based on current orchard profile
|
||||||
|
var context = new CompilationContext {
|
||||||
|
// Put most frequently used directories first in the list
|
||||||
|
DirectoriesToBrowse = new[] {
|
||||||
|
// Setup
|
||||||
|
"~/Modules/Orchard.Setup/Views",
|
||||||
|
"~/Themes/SafeMode/Views",
|
||||||
|
|
||||||
|
// Homepage
|
||||||
|
"~/Themes/TheThemeMachine/Views",
|
||||||
|
"~/Core/Common/Views",
|
||||||
|
"~/Core/Contents/Views",
|
||||||
|
"~/Core/Routable/Views",
|
||||||
|
"~/Core/Settings/Views",
|
||||||
|
"~/Core/Shapes/Views",
|
||||||
|
"~/Core/Feeds/Views",
|
||||||
|
"~/Modules/Orchard.Tags/Views",
|
||||||
|
"~/Modules/Orchard.Widgets/Views",
|
||||||
|
|
||||||
|
// Dashboard
|
||||||
|
"~/Core/Dashboard/Views",
|
||||||
|
"~/Themes/TheAdmin/Views",
|
||||||
|
|
||||||
|
// "Edit" homepage
|
||||||
|
"~/Modules/TinyMce/Views",
|
||||||
|
|
||||||
|
// Various other admin pages
|
||||||
|
"~/Modules/Orchard.Modules/Views",
|
||||||
|
"~/Modules/Orchard.Users/Views",
|
||||||
|
"~/Modules/Orchard.Media/Views",
|
||||||
|
"~/Modules/Orchard.Comments/Views",
|
||||||
|
|
||||||
|
// Leave these at end (as a best effort)
|
||||||
|
"~/Core", "~/Modules", "~/Themes"
|
||||||
|
},
|
||||||
|
FileExtensionsToCompile = new[] { ".cshtml", ".acsx", ".aspx" },
|
||||||
|
ProcessedDirectories = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
};
|
||||||
|
|
||||||
|
var directories = context
|
||||||
|
.DirectoriesToBrowse
|
||||||
|
.SelectMany(folder => GetViewDirectories(folder, context.FileExtensionsToCompile));
|
||||||
|
|
||||||
|
foreach (var viewDirectory in directories) {
|
||||||
|
if (_stopping) {
|
||||||
|
if (Logger.IsEnabled(LogLevel.Information)) {
|
||||||
|
var leftOvers = directories.Except(context.ProcessedDirectories).ToList();
|
||||||
|
Logger.Information("Background compilation stopped before all directories were processed ({0} directories left)", leftOvers.Count);
|
||||||
|
foreach (var directory in leftOvers) {
|
||||||
|
Logger.Information("Directory not processed: '{0}'", directory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompileDirectory(context, viewDirectory);
|
||||||
|
}
|
||||||
|
Logger.Information("Ending background compilation of views");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompileDirectory(CompilationContext context, string viewDirectory) {
|
||||||
|
// Prevent processing of the same directories multiple times (sligh performance optimization,
|
||||||
|
// as the build manager second call to compile a view is essentially a "no-op".
|
||||||
|
if (context.ProcessedDirectories.Contains(viewDirectory))
|
||||||
|
return;
|
||||||
|
context.ProcessedDirectories.Add(viewDirectory);
|
||||||
|
|
||||||
|
var stopwatch = new Stopwatch();
|
||||||
|
stopwatch.Start();
|
||||||
|
try {
|
||||||
|
|
||||||
|
var firstFile = _virtualPathProvider
|
||||||
|
.ListFiles(viewDirectory)
|
||||||
|
.Where(f => context.FileExtensionsToCompile.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase)))
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (firstFile != null)
|
||||||
|
BuildManager.GetCompiledAssembly(firstFile);
|
||||||
|
}
|
||||||
|
catch(Exception e) {
|
||||||
|
// Some views might not compile, this is ok and harmless in this
|
||||||
|
// context of pre-compiling views.
|
||||||
|
Logger.Information(e, "Compilation of directory '{0}' skipped", viewDirectory);
|
||||||
|
}
|
||||||
|
stopwatch.Stop();
|
||||||
|
Logger.Information("Directory '{0}' compiled in {1} msec", viewDirectory, stopwatch.ElapsedMilliseconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<string> GetViewDirectories(string directory, IEnumerable<string> extensions) {
|
||||||
|
var result = new List<string>();
|
||||||
|
GetViewDirectories(_virtualPathProvider, directory, extensions, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GetViewDirectories(IVirtualPathProvider vpp, string directory, IEnumerable<string> extensions, ICollection<string> files) {
|
||||||
|
if (vpp.ListFiles(directory).Where(f => extensions.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase))).Any()) {
|
||||||
|
files.Add(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var childDirectory in vpp.ListDirectories(directory).OrderBy(d => d, StringComparer.OrdinalIgnoreCase)) {
|
||||||
|
GetViewDirectories(vpp, childDirectory, extensions, files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -159,6 +159,7 @@
|
|||||||
<Compile Include="Environment\Extensions\Loaders\RawThemeExtensionLoader.cs" />
|
<Compile Include="Environment\Extensions\Loaders\RawThemeExtensionLoader.cs" />
|
||||||
<Compile Include="Environment\Features\FeatureManager.cs" />
|
<Compile Include="Environment\Features\FeatureManager.cs" />
|
||||||
<Compile Include="Environment\IAssemblyLoader.cs" />
|
<Compile Include="Environment\IAssemblyLoader.cs" />
|
||||||
|
<Compile Include="Environment\ViewsBackgroundCompilation.cs" />
|
||||||
<Compile Include="Environment\WorkContextImplementation.cs" />
|
<Compile Include="Environment\WorkContextImplementation.cs" />
|
||||||
<Compile Include="Environment\WorkContextModule.cs" />
|
<Compile Include="Environment\WorkContextModule.cs" />
|
||||||
<Compile Include="Environment\WorkContextProperty.cs" />
|
<Compile Include="Environment\WorkContextProperty.cs" />
|
||||||
|
Reference in New Issue
Block a user