diff --git a/src/DotVVM.Framework.Hosting.AspNetCore/ApplicationBuilderExtensions.cs b/src/DotVVM.Framework.Hosting.AspNetCore/ApplicationBuilderExtensions.cs index abd56e0c4b..397401b4f3 100644 --- a/src/DotVVM.Framework.Hosting.AspNetCore/ApplicationBuilderExtensions.cs +++ b/src/DotVVM.Framework.Hosting.AspNetCore/ApplicationBuilderExtensions.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Collections.Generic; +using System.Threading.Tasks; +using DotVVM.Framework.Compilation; using DotVVM.Framework.Configuration; using DotVVM.Framework.Hosting; using DotVVM.Framework.Hosting.Middlewares; @@ -65,7 +67,7 @@ private static DotvvmConfiguration UseDotVVM(this IApplicationBuilder app, strin modifyConfiguration?.Invoke(config); config.Freeze(); - + app.UseMiddleware(config, new List { ActivatorUtilities.CreateInstance(config.ServiceProvider), ActivatorUtilities.CreateInstance(app.ApplicationServices), @@ -73,6 +75,10 @@ private static DotvvmConfiguration UseDotVVM(this IApplicationBuilder app, strin new DotvvmReturnedFileMiddleware(), new DotvvmRoutingMiddleware() }.Where(t => t != null).ToArray()); + + var compilationConfiguration = config.Markup.ViewCompilation; + compilationConfiguration.HandleViewCompilation(config); + return config; } } diff --git a/src/DotVVM.Framework.Hosting.Owin/AppBuilderExtensions.cs b/src/DotVVM.Framework.Hosting.Owin/AppBuilderExtensions.cs index 1d71f31bf0..8f9a543836 100644 --- a/src/DotVVM.Framework.Hosting.Owin/AppBuilderExtensions.cs +++ b/src/DotVVM.Framework.Hosting.Owin/AppBuilderExtensions.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using DotVVM.Framework.Compilation; using DotVVM.Framework.Configuration; using DotVVM.Framework.Hosting; using DotVVM.Framework.Hosting.Middlewares; @@ -65,6 +68,7 @@ private static DotvvmConfiguration UseDotVVM(this IAppBuilder app, string applic s.TryAddSingleton(); s.TryAddScoped(_ => new DotvvmRequestContextStorage()); s.TryAddScoped(services => services.GetRequiredService().Context); + s.AddSingleton(); configurator?.ConfigureServices(new DotvvmServiceCollection(s)); }, serviceProviderFactoryMethod); config.Debug = debug; @@ -79,7 +83,7 @@ private static DotvvmConfiguration UseDotVVM(this IAppBuilder app, string applic modifyConfiguration?.Invoke(config); config.Freeze(); - + app.Use(config, new List { ActivatorUtilities.CreateInstance(config.ServiceProvider), ActivatorUtilities.CreateInstance(config.ServiceProvider), @@ -87,6 +91,10 @@ private static DotvvmConfiguration UseDotVVM(this IAppBuilder app, string applic new DotvvmReturnedFileMiddleware(), new DotvvmRoutingMiddleware() }.Where(t => t != null).ToArray()); + + var compilationConfiguration = config.Markup.ViewCompilation; + compilationConfiguration.HandleViewCompilation(config); + return config; } } diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.AuxOptions.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.AuxOptions.json index af59d918b6..003b372bb8 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.AuxOptions.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.AuxOptions.json @@ -5,7 +5,10 @@ { "namespace": "DotVVM.Framework.Binding.HelperNamespace" } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "resources": {}, "security": {}, diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.ExperimentalFeatures.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.ExperimentalFeatures.json index 542e5f22de..640b0d5487 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.ExperimentalFeatures.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.ExperimentalFeatures.json @@ -4,7 +4,10 @@ { "namespace": "DotVVM.Framework.Binding.HelperNamespace" } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "resources": {}, "security": {}, diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.Markup.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.Markup.json index 4f5b5182c4..1db663d7bf 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.Markup.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.Markup.json @@ -60,7 +60,10 @@ }, "Inherit": true } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "resources": {}, "security": {}, diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.RestAPI.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.RestAPI.json index e05bf096c1..b6c4ee2ab2 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.RestAPI.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.RestAPI.json @@ -19,7 +19,10 @@ }, "Inherit": true } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "resources": { "DotVVM.Framework.ResourceManagement.InlineScriptResource": { diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json index dc379fc7df..7faeb0af80 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeDefaultConfig.json @@ -11,7 +11,10 @@ { "namespace": "DotVVM.Framework.Binding.HelperNamespace" } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "resources": { "DotVVM.Framework.ResourceManagement.InlineScriptResource": { diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeEmptyConfig.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeEmptyConfig.json index 1cb4d0fe3f..12b0983b85 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeEmptyConfig.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeEmptyConfig.json @@ -4,7 +4,10 @@ { "namespace": "DotVVM.Framework.Binding.HelperNamespace" } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "resources": {}, "security": {}, diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeResources.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeResources.json index c9219bf39f..342355241e 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeResources.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeResources.json @@ -4,7 +4,10 @@ { "namespace": "DotVVM.Framework.Binding.HelperNamespace" } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "resources": { "DotVVM.Framework.ResourceManagement.InlineScriptResource": { diff --git a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeRoutes.json b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeRoutes.json index 0a409c5233..338c4ba471 100644 --- a/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeRoutes.json +++ b/src/DotVVM.Framework.Tests.Common/Runtime/config-tests/ConfigurationSerializationTests.SerializeRoutes.json @@ -4,7 +4,10 @@ { "namespace": "DotVVM.Framework.Binding.HelperNamespace" } - ] + ], + "ViewCompilation": { + "compileInParallel": true + } }, "routes": { "route1": { diff --git a/src/DotVVM.Framework/Compilation/DotvvmViewCompilationServiceExtensions.cs b/src/DotVVM.Framework/Compilation/DotvvmViewCompilationServiceExtensions.cs new file mode 100644 index 0000000000..752b22e99b --- /dev/null +++ b/src/DotVVM.Framework/Compilation/DotvvmViewCompilationServiceExtensions.cs @@ -0,0 +1,36 @@ +using System.Threading.Tasks; +using DotVVM.Framework.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace DotVVM.Framework.Compilation +{ + public static class DotvvmViewCompilationServiceExtensions + { + public static Task Precompile(this ViewCompilationConfiguration compilationConfiguration, + DotvvmConfiguration config) + { + return Task.Run(async () => { + var compilationService = config.ServiceProvider.GetService(); + if (compilationConfiguration.BackgroundCompilationDelay != null) + { + await Task.Delay(compilationConfiguration.BackgroundCompilationDelay.Value); + } + + await compilationService.CompileAll( + compilationConfiguration.CompileInParallel, false); + }); + } + + public static void HandleViewCompilation(this ViewCompilationConfiguration compilationConfiguration, DotvvmConfiguration config) + { + if (compilationConfiguration.Mode == ViewCompilationMode.Lazy) + return; + + var getCompilationTask = compilationConfiguration.Precompile(config); + if (compilationConfiguration.Mode == ViewCompilationMode.DuringApplicationStart) + { + getCompilationTask.Wait(); + } + } + } +} diff --git a/src/DotVVM.Framework/Compilation/ViewCompilationMode.cs b/src/DotVVM.Framework/Compilation/ViewCompilationMode.cs new file mode 100644 index 0000000000..25ce26ecbb --- /dev/null +++ b/src/DotVVM.Framework/Compilation/ViewCompilationMode.cs @@ -0,0 +1,21 @@ +namespace DotVVM.Framework.Compilation +{ + public enum ViewCompilationMode + { + /// + /// Compilation will be done when do markup is first needed. + /// + Lazy, + + /// + /// Compilation will run during application startup. + /// Application will start after compilation is done. + /// + DuringApplicationStart, + + /// + /// Compilation will run after application started. + /// + AfterApplicationStart + } +} diff --git a/src/DotVVM.Framework/Configuration/DotvvmMarkupConfiguration.cs b/src/DotVVM.Framework/Configuration/DotvvmMarkupConfiguration.cs index 150264c30b..57e94bd15e 100755 --- a/src/DotVVM.Framework/Configuration/DotvvmMarkupConfiguration.cs +++ b/src/DotVVM.Framework/Configuration/DotvvmMarkupConfiguration.cs @@ -69,6 +69,9 @@ public IList DefaultExtensionParameters } private IList _defaultExtensionParameters = new FreezableList(); + public ViewCompilationConfiguration ViewCompilation { get; private set; } = new ViewCompilationConfiguration(); + + public void AddServiceImport(string identifier, Type type) { ThrowIfFrozen(); @@ -173,7 +176,10 @@ private void ThrowIfFrozen() public void Freeze() { this.isFrozen = true; + + ViewCompilation.Freeze(); _controls.Freeze(); + foreach (var c in this.Controls) c.Freeze(); _assemblies.Freeze(); diff --git a/src/DotVVM.Framework/Configuration/ViewCompilationConfiguration.cs b/src/DotVVM.Framework/Configuration/ViewCompilationConfiguration.cs new file mode 100644 index 0000000000..2442462604 --- /dev/null +++ b/src/DotVVM.Framework/Configuration/ViewCompilationConfiguration.cs @@ -0,0 +1,73 @@ +using System; +using DotVVM.Framework.Compilation; +using Newtonsoft.Json; + +namespace DotVVM.Framework.Configuration +{ + public sealed class ViewCompilationConfiguration + { + private bool isFrozen = false; + private ViewCompilationMode mode; + /// + /// Gets or sets the mode under which the view compilation (pages, controls, ... ) is done. + /// + [JsonProperty("mode")] + public ViewCompilationMode Mode + { + get => mode; + set { + ThrowIfFrozen(); + mode = value; + } + } + + private TimeSpan? backgroundCompilationDelay; + /// + /// Gets or sets the delay before view compilation will be done. This compilation delay can be set only in precompilation modes. + /// + [JsonProperty("backgroundCompilationDelay")] + public TimeSpan? BackgroundCompilationDelay + { + get => backgroundCompilationDelay; + set + { + ThrowIfFrozen(); + backgroundCompilationDelay = value; + } + } + private bool compileInParallel = true; + /// + /// Gets or sets whether the view compilation will be performed in parallel or in series. + /// + [JsonProperty("compileInParallel")] + public bool CompileInParallel + { + get => compileInParallel; + set + { + ThrowIfFrozen(); + compileInParallel = value; + } + } + + public void Validate() + { + if (BackgroundCompilationDelay.HasValue && (Mode == ViewCompilationMode.Lazy || Mode==ViewCompilationMode.DuringApplicationStart)) + { + throw new Exception($"{nameof(BackgroundCompilationDelay)} must be null in {nameof(ViewCompilationMode.Lazy)} {nameof(Mode)}."); + } + } + + public void Freeze() + { + Validate(); + this.isFrozen = true; + } + + private void ThrowIfFrozen() + { + if (isFrozen) + throw FreezableUtils.Error(nameof(DotvvmMarkupConfiguration)); + } + } +} diff --git a/src/DotVVM.Framework/Controls/UpdateProgress.cs b/src/DotVVM.Framework/Controls/UpdateProgress.cs index ad3f3b2903..e8990cc13a 100644 --- a/src/DotVVM.Framework/Controls/UpdateProgress.cs +++ b/src/DotVVM.Framework/Controls/UpdateProgress.cs @@ -93,7 +93,7 @@ public static IEnumerable ValidateUsage(ResolvedControl contr { if ((int)delayProperty.Value < 0) { - yield return new ControlUsageError("Delay cannot be set to negative number."); + yield return new ControlUsageError($"{nameof(Delay)} cannot be set to negative number."); } } @@ -102,15 +102,15 @@ public static IEnumerable ValidateUsage(ResolvedControl contr var excludedQueues = (control.GetValue(ExcludedQueuesProperty) as ResolvedPropertyValue)?.Value as string[]; if (includedQueues != null && excludedQueues != null) { - yield return new ControlUsageError("The IncludedQueues and ExcludedQueues cannot be used together!"); + yield return new ControlUsageError($"The {nameof(IncludedQueues)} and {nameof(ExcludedQueues)} cannot be used together!"); } if (includedQueues != null && !ValidateQueueNames(includedQueues)) { - yield return new ControlUsageError("The IncludedQueues must contain comma-separated list of queue names (which can contain alphanumeric characters, underscore or dash)!"); + yield return new ControlUsageError($"The {nameof(IncludedQueues)} must contain comma-separated list of queue names (which can contain alphanumeric characters, underscore or dash)!"); } if (excludedQueues != null && !ValidateQueueNames(excludedQueues)) { - yield return new ControlUsageError("The ExcludedQueues must contain comma-separated list of queue names (which can contain alphanumeric characters, underscore or dash)!"); + yield return new ControlUsageError($"The {nameof(ExcludedQueues)} must contain comma-separated list of queue names (which can contain alphanumeric characters, underscore or dash)!"); } }