mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-04-05 21:01:35 +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:
parent
907c6481c8
commit
e88e52b280
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Web.Mvc;
|
||||
using Orchard.Environment;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Setup.Services;
|
||||
using Orchard.Setup.ViewModels;
|
||||
@ -11,11 +12,13 @@ namespace Orchard.Setup.Controllers {
|
||||
[ValidateInput(false), Themed]
|
||||
public class SetupController : Controller {
|
||||
private readonly IAppDataFolder _appDataFolder;
|
||||
private readonly IViewsBackgroundCompilation _viewsBackgroundCompilation;
|
||||
private readonly INotifier _notifier;
|
||||
private readonly ISetupService _setupService;
|
||||
|
||||
public SetupController(INotifier notifier, ISetupService setupService, IAppDataFolder appDataFolder) {
|
||||
public SetupController(INotifier notifier, ISetupService setupService, IAppDataFolder appDataFolder, IViewsBackgroundCompilation viewsBackgroundCompilation) {
|
||||
_appDataFolder = appDataFolder;
|
||||
_viewsBackgroundCompilation = viewsBackgroundCompilation;
|
||||
_notifier = notifier;
|
||||
_setupService = setupService;
|
||||
T = NullLocalizer.Instance;
|
||||
@ -37,6 +40,17 @@ namespace Orchard.Setup.Controllers {
|
||||
|
||||
public ActionResult Index() {
|
||||
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)});
|
||||
}
|
||||
|
||||
@ -75,6 +89,11 @@ namespace Orchard.Setup.Controllers {
|
||||
|
||||
_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.
|
||||
return Redirect("~/");
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ namespace Orchard.Environment {
|
||||
builder.RegisterType<DefaultProjectFileParser>().As<IProjectFileParser>().SingleInstance();
|
||||
builder.RegisterType<DefaultAssemblyLoader>().As<IAssemblyLoader>().SingleInstance();
|
||||
builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>().SingleInstance();
|
||||
builder.RegisterType<ViewsBackgroundCompilation>().As<IViewsBackgroundCompilation>().SingleInstance();
|
||||
|
||||
RegisterVolatileProvider<WebSiteFolder, IWebSiteFolder>(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\Features\FeatureManager.cs" />
|
||||
<Compile Include="Environment\IAssemblyLoader.cs" />
|
||||
<Compile Include="Environment\ViewsBackgroundCompilation.cs" />
|
||||
<Compile Include="Environment\WorkContextImplementation.cs" />
|
||||
<Compile Include="Environment\WorkContextModule.cs" />
|
||||
<Compile Include="Environment\WorkContextProperty.cs" />
|
||||
|
Loading…
Reference in New Issue
Block a user