Skip to content

Commit

Permalink
Merge pull request #1755 from riganti/dothtml-reload-fflag
Browse files Browse the repository at this point in the history
feature flag to reenable dothtml reloading in Production environment
  • Loading branch information
tomasherceg authored Jan 12, 2024
2 parents 955698c + 7a473c8 commit 693317e
Show file tree
Hide file tree
Showing 23 changed files with 451 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ public class DefaultControlResolver : ControlResolverBase
private readonly DotvvmConfiguration configuration;
private readonly IControlBuilderFactory controlBuilderFactory;
private readonly CompiledAssemblyCache compiledAssemblyCache;
private readonly Dictionary<string, Type>? controlNameMappings;

private static object locker = new object();
private static bool isInitialized = false;
private static object dotvvmLocker = new object();
private static bool isDotvvmInitialized = false;

private static Dictionary<string, Type>? controlNameMappings;

public DefaultControlResolver(DotvvmConfiguration configuration, IControlBuilderFactory controlBuilderFactory, CompiledAssemblyCache compiledAssemblyCache) : base(configuration.Markup)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class DefaultControlBuilderFactory : IControlBuilderFactory
public DefaultControlBuilderFactory(DotvvmConfiguration configuration, IMarkupFileLoader markupFileLoader, CompiledAssemblyCache compiledAssemblyCache)
{
this.configuration = configuration;
this.allowReload = configuration.Debug;
this.allowReload = configuration.Runtime.ReloadMarkupFiles.Enabled ?? configuration.Debug;

// WORKAROUND: there is a circular dependency
// TODO: get rid of that
Expand Down
206 changes: 206 additions & 0 deletions src/Framework/Framework/Configuration/Dotvvm3StateFeatureFlag.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Newtonsoft.Json;

namespace DotVVM.Framework.Configuration
{
/// <summary> Overrides an automatically enabled feature by always enabling or disabling it for the entire application or only for certain routes. </summary>
public class Dotvvm3StateFeatureFlag: IDotvvmFeatureFlagAdditiveConfiguration
{
[JsonIgnore]
public string FlagName { get; }

public Dotvvm3StateFeatureFlag(string flagName)
{
FlagName = flagName;
}

/// <summary> Default state of this feature flag. true = enabled, false = disabled, null = enabled automatically based on other conditions (usually running in Development/Production environment) </summary>
[JsonProperty("enabled")]
public bool? Enabled
{
get => _enabled;
set
{
ThrowIfFrozen();
_enabled = value;
}
}
private bool? _enabled = null;

/// <summary> List of routes where the feature flag is always enabled. </summary>
[JsonProperty("includedRoutes")]
public ISet<string> IncludedRoutes
{
get => _includedRoutes;
set
{
ThrowIfFrozen();
_includedRoutes = value;
}
}
private ISet<string> _includedRoutes = new FreezableSet<string>(comparer: StringComparer.OrdinalIgnoreCase);

/// <summary> List of routes where the feature flag is always disabled. </summary>
[JsonProperty("excludedRoutes")]
public ISet<string> ExcludedRoutes
{
get => _excludedRoutes;
set
{
ThrowIfFrozen();
_excludedRoutes = value;
}
}
private ISet<string> _excludedRoutes = new FreezableSet<string>(comparer: StringComparer.OrdinalIgnoreCase);

/// <summary> Resets the feature flag to its default state. </summary>
public IDotvvmFeatureFlagAdditiveConfiguration Reset()
{
ThrowIfFrozen();
IncludedRoutes.Clear();
ExcludedRoutes.Clear();
Enabled = null;
return this;
}

/// <summary> Enables the feature flag for all routes, even if it has been previously disabled. </summary>
public IDotvvmFeatureFlagAdditiveConfiguration EnableForAllRoutes()
{
ThrowIfFrozen();
IncludedRoutes.Clear();
ExcludedRoutes.Clear();
Enabled = true;
return this;
}

/// <summary> Disables the feature flag for all routes, even if it has been previously enabled. </summary>
public IDotvvmFeatureFlagAdditiveConfiguration DisableForAllRoutes()
{
ThrowIfFrozen();
IncludedRoutes.Clear();
ExcludedRoutes.Clear();
Enabled = false;
return this;
}

/// <summary> Enables the feature flag only for the specified routes, and disables for all other (Clears any previous rules). </summary>
public void EnableForRoutes(params string[] routes)
{
ThrowIfFrozen();
Enabled = false;
ExcludedRoutes.Clear();

IncludedRoutes.Clear();
foreach (var route in routes)
{
IncludedRoutes.Add(route);
}
}

/// <summary> Enables the feature flag for all routes except the specified ones (Clears any previous rules). </summary>
public void EnableForAllRoutesExcept(params string[] routes)
{
ThrowIfFrozen();
Enabled = true;
IncludedRoutes.Clear();

ExcludedRoutes.Clear();
foreach (var route in routes)
{
ExcludedRoutes.Add(route);
}
}

/// <summary> Include the specified route in this feature flag. Enables the feature for the route. </summary>
public IDotvvmFeatureFlagAdditiveConfiguration IncludeRoute(string routeName)
{
ThrowIfFrozen();
if (Enabled == true)
throw new InvalidOperationException($"Cannot include route '{routeName}' because the feature flag {this.FlagName} is enabled by default.");
if (ExcludedRoutes.Contains(routeName))
throw new InvalidOperationException($"Cannot include route '{routeName}' because it is already in the list of excluded routes.");
IncludedRoutes.Add(routeName);
return this;
}
/// <summary> Include the specified routes in this feature flag. Enables the feature for the routes. </summary>
public IDotvvmFeatureFlagAdditiveConfiguration IncludeRoutes(params string[] routeNames)
{
foreach (var routeName in routeNames)
{
IncludeRoute(routeName);
}
return this;
}
/// <summary> Exclude the specified route from this feature flag. Disables the feature for the route. </summary>
public IDotvvmFeatureFlagAdditiveConfiguration ExcludeRoute(string routeName)
{
ThrowIfFrozen();
if (Enabled == false)
throw new InvalidOperationException($"Cannot exclude route '{routeName}' because the feature flag {this.FlagName} is disabled by default.");
if (IncludedRoutes.Contains(routeName))
throw new InvalidOperationException($"Cannot exclude route '{routeName}' because it is already in the list of included routes.");
ExcludedRoutes.Add(routeName);
return this;
}
/// <summary> Exclude the specified routes from this feature flag. Disables the feature for the routes. </summary>
public IDotvvmFeatureFlagAdditiveConfiguration ExcludeRoutes(params string[] routeNames)
{
foreach (var routeName in routeNames)
{
ExcludeRoute(routeName);
}
return this;
}

/// <summary> Return true if there exists a route where this feature flag is enabled. </summary>
public bool IsEnabledForAnyRoute(bool defaultValue)
{
if (Enabled == false && IncludedRoutes.Count == 0)
return false;
return defaultValue || Enabled == true || IncludedRoutes.Count > 0;
}

/// <summary> Returns true/false if this feature flag has been explicitly enabled/disabled for the specified route. </summary>
public bool? IsEnabledForRoute(string? routeName)
{
if (IncludedRoutes.Contains(routeName!))
return true;
if (ExcludedRoutes.Contains(routeName!))
return false;
return Enabled;
}

/// <summary> Returns if this feature flag has been explicitly for the specified route. </summary>
public bool IsEnabledForRoute(string? routeName, bool defaultValue)
{
defaultValue = Enabled ?? defaultValue;
if (defaultValue)
return !ExcludedRoutes.Contains(routeName!);
else
return IncludedRoutes.Contains(routeName!);
}

private bool isFrozen = false;
private void ThrowIfFrozen()
{
if (isFrozen)
throw FreezableUtils.Error($"{nameof(DotvvmFeatureFlag)} {this.FlagName}");
}
public void Freeze()
{
this.isFrozen = true;
FreezableSet.Freeze(ref this._excludedRoutes);
FreezableSet.Freeze(ref this._includedRoutes);
}

public override string ToString()
{
var defaultStr = Enabled switch { null => "Default state", true => "Enabled by default", false => "Disabled by default" };
var enabledStr = IncludedRoutes.Count > 0 ? $", enabled for routes: [{string.Join(", ", IncludedRoutes)}]" : null;
var disabledStr = ExcludedRoutes.Count > 0 ? $", disabled for routes: [{string.Join(", ", ExcludedRoutes)}]" : null;
return $"Feature flag {this.FlagName}: {defaultStr}{enabledStr}{disabledStr}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ private void ThrowIfFrozen()
public void Freeze()
{
isFrozen = true;
Diagnostics.Freeze();
Markup.Freeze();
RouteTable.Freeze();
Resources.Freeze();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public void Validate()
private void ThrowIfFrozen()
{
if (isFrozen)
FreezableUtils.Error(nameof(DotvvmControlConfiguration));
throw FreezableUtils.Error(nameof(DotvvmControlConfiguration));
}
public void Freeze()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Framework/Framework/Configuration/DotvvmFeatureFlag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public bool IsEnabledForRoute(string? routeName)
private void ThrowIfFrozen()
{
if (isFrozen)
FreezableUtils.Error($"{nameof(DotvvmFeatureFlag)} {this.FlagName}");
throw FreezableUtils.Error($"{nameof(DotvvmFeatureFlag)} {this.FlagName}");
}
public void Freeze()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using Newtonsoft.Json;

namespace DotVVM.Framework.Configuration
{
/// <summary> Overrides an automatically enabled feature by always enabling or disabling it for the entire application. </summary>
public class DotvvmGlobal3StateFeatureFlag
{
[JsonIgnore]
public string FlagName { get; }

public DotvvmGlobal3StateFeatureFlag(string flagName)
{
FlagName = flagName;
}

/// <summary> Gets or sets whether the feature is enabled or disabled. </summary>
[JsonProperty("enabled")]
public bool? Enabled
{
get => _enabled;
set
{
ThrowIfFrozen();
_enabled = value;
}
}
private bool? _enabled = null;

/// <summary> Resets the feature flag to its default state. </summary>
public void Reset()
{
ThrowIfFrozen();
Enabled = null;
}

/// <summary> Enables the feature for this application. </summary>
public void Enable()
{
ThrowIfFrozen();
Enabled = true;
}
/// <summary> Disables the feature for this application </summary>
public void Disable()
{
ThrowIfFrozen();
Enabled = false;
}

private bool isFrozen = false;
private void ThrowIfFrozen()
{
if (isFrozen)
throw FreezableUtils.Error($"{nameof(DotvvmGlobalFeatureFlag)} {this.FlagName}");
}

public void Freeze()
{
this.isFrozen = true;
}

public override string ToString() => $"Feature flag {FlagName}: {Enabled switch { null => "Default", true => "Enabled", false => "Disabled"}}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void Disable()
private void ThrowIfFrozen()
{
if (isFrozen)
FreezableUtils.Error($"{nameof(DotvvmGlobalFeatureFlag)} {this.FlagName}");
throw FreezableUtils.Error($"{nameof(DotvvmGlobalFeatureFlag)} {this.FlagName}");
}

public void Freeze()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ public void Freeze()
foreach (var t in this.HtmlAttributeTransforms)
t.Value.Freeze();
_defaultDirectives.Freeze();

FreezableList.Freeze(ref _importedNamespaces);
JavascriptTranslator.Freeze();
FreezableList.Freeze(ref _defaultExtensionParameters);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Linq;
using Newtonsoft.Json;
using DotVVM.Framework.Runtime.Filters;
using DotVVM.Framework.Runtime.Tracing;

namespace DotVVM.Framework.Configuration
{
Expand All @@ -16,6 +15,13 @@ public class DotvvmRuntimeConfiguration
public IList<IActionFilter> GlobalFilters => _globalFilters;
private IList<IActionFilter> _globalFilters;

/// <summary>
/// When enabled, dothtml files are reloaded and recompiled after a change. Note that resources (CSS, JS) are not controlled by this option.
/// By default, reloading is only enabled in debug mode.
/// </summary>
[JsonProperty("reloadMarkupFiles")]
public DotvvmGlobal3StateFeatureFlag ReloadMarkupFiles { get; } = new("Dotvvm3StateFeatureFlag.ReloadMarkupFiles");

/// <summary>
/// Initializes a new instance of the <see cref="DotvvmRuntimeConfiguration"/> class.
/// </summary>
Expand All @@ -35,6 +41,7 @@ public void Freeze()
{
this.isFrozen = true;
FreezableList.Freeze(ref _globalFilters);
ReloadMarkupFiles.Freeze();
}
}
}
Loading

0 comments on commit 693317e

Please sign in to comment.