From 831cdb1f11c5e08376492136abdbaa6200d5297c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg=20=28RIGANTI=20s=2Er=2Eo=2E=29?= Date: Sun, 3 Nov 2024 16:20:56 +0100 Subject: [PATCH 1/4] Fixed .NET Framework binding redirects --- src/Samples/Api.Owin/Web.config | 24 +++++++++++++++++++ .../ApplicationInsights.Owin/Web.config | 8 ++++++- src/Samples/MiniProfiler.Owin/Web.config | 6 +++++ src/Samples/Owin/Web.config | 24 +++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/Samples/Api.Owin/Web.config b/src/Samples/Api.Owin/Web.config index 0be87e234..27da703e0 100644 --- a/src/Samples/Api.Owin/Web.config +++ b/src/Samples/Api.Owin/Web.config @@ -78,6 +78,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Samples/ApplicationInsights.Owin/Web.config b/src/Samples/ApplicationInsights.Owin/Web.config index 1aac37a74..75cf1c4d1 100644 --- a/src/Samples/ApplicationInsights.Owin/Web.config +++ b/src/Samples/ApplicationInsights.Owin/Web.config @@ -155,5 +155,11 @@ + + + + + + - + \ No newline at end of file diff --git a/src/Samples/MiniProfiler.Owin/Web.config b/src/Samples/MiniProfiler.Owin/Web.config index 9bed4302e..a3d61d26f 100644 --- a/src/Samples/MiniProfiler.Owin/Web.config +++ b/src/Samples/MiniProfiler.Owin/Web.config @@ -83,5 +83,11 @@ + + + + + + \ No newline at end of file diff --git a/src/Samples/Owin/Web.config b/src/Samples/Owin/Web.config index 5def7fcb2..f5b701beb 100644 --- a/src/Samples/Owin/Web.config +++ b/src/Samples/Owin/Web.config @@ -77,5 +77,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 11784bbece77c9efff84b7f64bddd13d5e288b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg=20=28RIGANTI=20s=2Er=2Eo=2E=29?= Date: Sun, 3 Nov 2024 16:25:26 +0100 Subject: [PATCH 2/4] Fixed route and url redirections inside route groups --- .../Configuration/FreezableDictionary.cs | 6 +- .../Framework/Controls/RouteLinkHelpers.cs | 2 +- .../LocalResourceUrlManager.cs | 1 + .../Framework/Routing/DefaultRouteStrategy.cs | 7 +- .../Framework/Routing/DotvvmRoute.cs | 39 +----- .../Framework/Routing/DotvvmRouteParser.cs | 4 +- .../Framework/Routing/DotvvmRouteTable.cs | 129 +++++++++--------- .../Framework/Routing/LocalizedDotvvmRoute.cs | 29 +--- src/Framework/Framework/Routing/RouteBase.cs | 59 ++++---- .../Framework/Routing/RouteHelper.cs | 2 +- .../Framework/Routing/RouteTableGroup.cs | 4 +- .../Routing/RouteTableJsonConverter.cs | 47 ------- src/Framework/Testing/ControlTestHelper.cs | 2 +- src/Tests/Routing/DotvvmRouteTests.cs | 114 ++++++++-------- src/Tests/Routing/RouteSerializationTests.cs | 4 +- src/Tests/Routing/RouteTableGroupTests.cs | 52 +++++-- .../Runtime/DotvvmControlRenderedHtmlTests.cs | 2 +- 17 files changed, 218 insertions(+), 285 deletions(-) diff --git a/src/Framework/Framework/Configuration/FreezableDictionary.cs b/src/Framework/Framework/Configuration/FreezableDictionary.cs index 5b1f937ab..2d995ee53 100644 --- a/src/Framework/Framework/Configuration/FreezableDictionary.cs +++ b/src/Framework/Framework/Configuration/FreezableDictionary.cs @@ -23,7 +23,7 @@ public static void Freeze([AllowNull] ref IDictionary dict) } } } - sealed class FreezableDictionary : IDictionary, IReadOnlyCollection> + sealed class FreezableDictionary : IDictionary, IReadOnlyCollection>, IReadOnlyDictionary where K : notnull { private readonly Dictionary dict; @@ -102,5 +102,9 @@ public V this[K index] public ICollection Keys => ((IDictionary)dict).Keys.ToArray(); public ICollection Values => ((IDictionary)dict).Values.ToArray(); + + IEnumerable IReadOnlyDictionary.Keys => Keys; + + IEnumerable IReadOnlyDictionary.Values => Values; } } diff --git a/src/Framework/Framework/Controls/RouteLinkHelpers.cs b/src/Framework/Framework/Controls/RouteLinkHelpers.cs index e6a839552..0748eefd6 100644 --- a/src/Framework/Framework/Controls/RouteLinkHelpers.cs +++ b/src/Framework/Framework/Controls/RouteLinkHelpers.cs @@ -194,7 +194,7 @@ private static void EnsureValidBindingType(IBinding binding) private static Dictionary ComposeNewRouteParameters(RouteLink control, IDotvvmRequestContext context, RouteBase route) { - var parameters = new Dictionary(route.DefaultValues, StringComparer.OrdinalIgnoreCase); + var parameters = route.CloneDefaultValues(); foreach (var param in context.Parameters!) { parameters[param.Key] = param.Value; diff --git a/src/Framework/Framework/ResourceManagement/LocalResourceUrlManager.cs b/src/Framework/Framework/ResourceManagement/LocalResourceUrlManager.cs index c740bbfa9..fe3505e06 100644 --- a/src/Framework/Framework/ResourceManagement/LocalResourceUrlManager.cs +++ b/src/Framework/Framework/ResourceManagement/LocalResourceUrlManager.cs @@ -28,6 +28,7 @@ public LocalResourceUrlManager(DotvvmConfiguration configuration, IResourceHashS this.resourceRoute = new DotvvmRoute( url: $"{HostingConstants.ResourceRouteName}/{{{HashParameterName}}}/{{{NameParameterName}:regex(.*)}}", virtualPath: "", + name: $"_dotvvm_{nameof(LocalResourceUrlManager)}", defaultValues: null, presenterFactory: _ => throw new NotSupportedException(), configuration: configuration); diff --git a/src/Framework/Framework/Routing/DefaultRouteStrategy.cs b/src/Framework/Framework/Routing/DefaultRouteStrategy.cs index db62a636e..63c29e0ca 100644 --- a/src/Framework/Framework/Routing/DefaultRouteStrategy.cs +++ b/src/Framework/Framework/Routing/DefaultRouteStrategy.cs @@ -107,10 +107,7 @@ protected virtual RouteBase BuildRoute(RouteStrategyMarkupFileInfo file) var defaultParameters = GetRouteDefaultParameters(file); var presenterFactory = GetRoutePresenterFactory(file); - return new DotvvmRoute(url, file.AppRelativePath, defaultParameters, presenterFactory, configuration) - { - RouteName = routeName - }; + return new DotvvmRoute(url, file.AppRelativePath, routeName, defaultParameters, presenterFactory, configuration); } protected virtual string GetRouteName(RouteStrategyMarkupFileInfo file) @@ -162,7 +159,7 @@ private static IEnumerable GetRoutesForFile(string fileName) protected override IEnumerable BuildRoutes(RouteStrategyMarkupFileInfo file) { return getRouteList(file.AppRelativePath) - .Select(url => new DotvvmRoute(url, file.AppRelativePath, GetRouteDefaultParameters(file), GetRoutePresenterFactory(file), this.configuration) { RouteName = url }); + .Select(url => new DotvvmRoute(url, file.AppRelativePath, url, GetRouteDefaultParameters(file), GetRoutePresenterFactory(file), this.configuration)); } } } diff --git a/src/Framework/Framework/Routing/DotvvmRoute.cs b/src/Framework/Framework/Routing/DotvvmRoute.cs index fc5102c93..e0ee395e4 100644 --- a/src/Framework/Framework/Routing/DotvvmRoute.cs +++ b/src/Framework/Framework/Routing/DotvvmRoute.cs @@ -1,21 +1,15 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using DotVVM.Framework.Hosting; using System.Text.RegularExpressions; using DotVVM.Framework.Configuration; using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using DotVVM.Framework.Utils; namespace DotVVM.Framework.Routing { public sealed class DotvvmRoute : RouteBase { - private Func presenterFactory; - private Regex routeRegex; private List, string>> urlBuilders; private List?>> parameters; @@ -35,42 +29,26 @@ public sealed class DotvvmRoute : RouteBase /// /// Initializes a new instance of the class. /// -#pragma warning disable CS8618 - public DotvvmRoute(string url, string virtualPath, object? defaultValues, Func presenterFactory, DotvvmConfiguration configuration) - : base(url, virtualPath, defaultValues) - { - this.presenterFactory = presenterFactory; - - ParseRouteUrl(configuration); - } - - /// - /// Initializes a new instance of the class. - /// - public DotvvmRoute(string url, string virtualPath, IDictionary? defaultValues, Func presenterFactory, DotvvmConfiguration configuration) - : base(url, virtualPath, defaultValues) + public DotvvmRoute(string url, string? virtualPath, string name, object? defaultValues, Func presenterFactory, DotvvmConfiguration configuration) + : base(url, virtualPath, name, defaultValues, presenterFactory) { - this.presenterFactory = presenterFactory; - ParseRouteUrl(configuration); } /// /// Initializes a new instance of the class. /// - public DotvvmRoute(string url, string virtualPath, string name, IDictionary? defaultValues, Func presenterFactory, DotvvmConfiguration configuration) - : base(url, virtualPath, name, defaultValues) + public DotvvmRoute(string url, string? virtualPath, string name, IDictionary? defaultValues, Func presenterFactory, DotvvmConfiguration configuration) + : base(url, virtualPath, name, defaultValues, presenterFactory) { - this.presenterFactory = presenterFactory; - ParseRouteUrl(configuration); } -#pragma warning restore CS8618 /// /// Parses the route URL and extracts the components. /// + [MemberNotNull(nameof(routeRegex), nameof(urlBuilders), nameof(parameters), nameof(parameterMetadata), nameof(urlWithoutTypes))] private void ParseRouteUrl(DotvvmConfiguration configuration) { var parser = new DotvvmRouteParser(configuration.RouteConstraints); @@ -99,8 +77,7 @@ public override bool IsMatch(string url, [MaybeNullWhen(false)] out IDictionary< return false; } - values = new Dictionary(DefaultValues, StringComparer.OrdinalIgnoreCase); - + values = CloneDefaultValues(); foreach (var parameter in parameters) { var g = match.Groups["param" + parameter.Key]; @@ -155,10 +132,6 @@ protected internal override string BuildUrlCore(Dictionary valu } } - /// - /// Processes the request. - /// - public override IDotvvmPresenter GetPresenter(IServiceProvider provider) => presenterFactory(provider); protected override void Freeze2() { // there is no property that would have to be frozen diff --git a/src/Framework/Framework/Routing/DotvvmRouteParser.cs b/src/Framework/Framework/Routing/DotvvmRouteParser.cs index 123ae0caa..4711ddaa9 100644 --- a/src/Framework/Framework/Routing/DotvvmRouteParser.cs +++ b/src/Framework/Framework/Routing/DotvvmRouteParser.cs @@ -16,7 +16,7 @@ public DotvvmRouteParser(IDictionary routeCon this.routeConstraints = routeConstrains; } - public UrlParserResult ParseRouteUrl(string url, IDictionary defaultValues) + public UrlParserResult ParseRouteUrl(string url, IReadOnlyDictionary defaultValues) { if (url.StartsWith("/", StringComparison.Ordinal)) throw new ArgumentException("The route URL must not start with '/'!"); @@ -85,7 +85,7 @@ void AppendParameterParserResult(UrlParameterParserResult result) }; } - private UrlParameterParserResult ParseParameter(string url, string prefix, ref int index, IDictionary defaultValues) + private UrlParameterParserResult ParseParameter(string url, string prefix, ref int index, IReadOnlyDictionary defaultValues) { // find the end of the route parameter name var startIndex = index; diff --git a/src/Framework/Framework/Routing/DotvvmRouteTable.cs b/src/Framework/Framework/Routing/DotvvmRouteTable.cs index fd0bd1241..f8a185f5f 100644 --- a/src/Framework/Framework/Routing/DotvvmRouteTable.cs +++ b/src/Framework/Framework/Routing/DotvvmRouteTable.cs @@ -117,11 +117,8 @@ public IDotvvmPresenter GetDefaultPresenter(IServiceProvider provider) /// The virtual path of the Dothtml file. /// The default values. /// Delegate creating the presenter handling this route - public void Add(string routeName, string? url, string virtualPath, object? defaultValues = null, Func? presenterFactory = null, LocalizedRouteUrl[]? localizedUrls = null) + public void Add(string routeName, string url, string? virtualPath, object? defaultValues = null, Func? presenterFactory = null, LocalizedRouteUrl[]? localizedUrls = null) { - ThrowIfFrozen(); - - virtualPath = CombinePath(group?.VirtualPathPrefix, virtualPath); AddCore(routeName, url, virtualPath, defaultValues, presenterFactory, localizedUrls); } @@ -132,26 +129,74 @@ public void Add(string routeName, string? url, string virtualPath, object? defau /// The URL. /// The default values. /// The presenter factory. - public void Add(string routeName, string? url, Func? presenterFactory = null, object? defaultValues = null, LocalizedRouteUrl[]? localizedUrls = null) + public void Add(string routeName, string url, Func presenterFactory, object? defaultValues = null, LocalizedRouteUrl[]? localizedUrls = null) + { + AddCore(routeName, url, virtualPath: null, defaultValues, presenterFactory, localizedUrls); + } + + /// + /// Adds the specified route name. + /// + /// Name of the route. + /// The URL. + /// The presenter factory. + /// The default values. + public void Add(string routeName, string url, Type presenterType, object? defaultValues = null, LocalizedRouteUrl[]? localizedUrls = null) + { + if (!typeof(IDotvvmPresenter).IsAssignableFrom(presenterType)) + { + throw new ArgumentException($@"{nameof(presenterType)} has to inherit from DotVVM.Framework.Hosting.IDotvvmPresenter.", nameof(presenterType)); + } + Func presenterFactory = provider => (IDotvvmPresenter)provider.GetRequiredService(presenterType); + AddCore(routeName, url, virtualPath: null, defaultValues, presenterFactory, localizedUrls); + } + + /// + /// Adds the specified name. + /// + public void Add(RouteBase route) { ThrowIfFrozen(); + if (dictionary.ContainsKey(route.RouteName)) + { + throw new InvalidOperationException($"The route with name '{route.RouteName}' has already been registered!"); + } - var virtualPath = group?.VirtualPathPrefix ?? ""; - AddCore(routeName, url, virtualPath, defaultValues, presenterFactory, localizedUrls); + group?.AddToParentRouteTable?.Invoke(route); + + // The list is used for finding the routes because it keeps the ordering, the dictionary is for checking duplicates + list.Add(new KeyValuePair(route.RouteName, route)); + dictionary.Add(route.RouteName, route); + + if (route is IPartialMatchRoute partialMatchRoute) + { + partialMatchRoutes.Add(partialMatchRoute); + } } - private void AddCore(string routeName, string? url, string virtualPath, object? defaultValues, Func? presenterFactory, LocalizedRouteUrl[]? localizedUrls) + private void AddCore(string routeName, string url, string? virtualPath, object? defaultValues, Func? presenterFactory, LocalizedRouteUrl[]? localizedUrls = null) { + ThrowIfFrozen(); + + if (url == null) + throw new ArgumentNullException(nameof(url)); url = CombinePath(group?.UrlPrefix, url); + + virtualPath = CombinePath(group?.VirtualPathPrefix, virtualPath); + if (virtualPath == null && presenterFactory == null) + { + throw new ArgumentNullException(nameof(presenterFactory), "The presenterFactory argument must be set when virtualPath is null!"); + } + presenterFactory ??= GetDefaultPresenter; routeName = group?.RouteNamePrefix + routeName; RouteBase route = localizedUrls == null - ? new DotvvmRoute(url, virtualPath, defaultValues, presenterFactory, configuration) + ? new DotvvmRoute(url, virtualPath, routeName, defaultValues, presenterFactory, configuration) : new LocalizedDotvvmRoute(url, localizedUrls.Select(l => new LocalizedRouteUrl(l.CultureIdentifier, CombinePath(group?.UrlPrefix, l.RouteUrl))).ToArray(), - virtualPath, defaultValues, presenterFactory, configuration); - Add(routeName, route); + virtualPath, routeName, defaultValues, presenterFactory, configuration); + Add(route); } /// @@ -171,7 +216,6 @@ public void AddUrlRedirection(string routeName, string urlPattern, string target /// URL provider to obtain context-based redirection targets. public void AddUrlRedirection(string routeName, string urlPattern, Func targetUrlProvider, object? defaultValues = null, bool permanent = false) { - ThrowIfFrozen(); IDotvvmPresenter presenterFactory(IServiceProvider serviceProvider) => new DelegatePresenter(context => { var targetUrl = targetUrlProvider(context); @@ -181,7 +225,7 @@ public void AddUrlRedirection(string routeName, string urlPattern, Func @@ -207,7 +251,6 @@ public void AddRouteRedirection(string routeName, string urlPattern, Func>? parametersProvider = null, Func? urlSuffixProvider = null) { - ThrowIfFrozen(); IDotvvmPresenter presenterFactory(IServiceProvider serviceProvider) => new DelegatePresenter(context => { var targetRouteName = targetRouteNameProvider(context); @@ -219,50 +262,7 @@ public void AddRouteRedirection(string routeName, string urlPattern, Func - /// Adds the specified route name. - /// - /// Name of the route. - /// The URL. - /// The presenter factory. - /// The default values. - public void Add(string routeName, string? url, Type presenterType, object? defaultValues = null, LocalizedRouteUrl[]? localizedUrls = null) - { - ThrowIfFrozen(); - if (!typeof(IDotvvmPresenter).IsAssignableFrom(presenterType)) - { - throw new ArgumentException($@"{nameof(presenterType)} has to inherit from DotVVM.Framework.Hosting.IDotvvmPresenter.", nameof(presenterType)); - } - Func presenterFactory = provider => (IDotvvmPresenter)provider.GetRequiredService(presenterType); - Add(routeName, url, presenterFactory, defaultValues, localizedUrls); - } - - /// - /// Adds the specified name. - /// - public void Add(string routeName, RouteBase route) - { - ThrowIfFrozen(); - if (dictionary.ContainsKey(routeName)) - { - throw new InvalidOperationException($"The route with name '{routeName}' has already been registered!"); - } - // internal assign routename - route.RouteName = routeName; - - group?.AddToParentRouteTable?.Invoke(routeName, route); - - // The list is used for finding the routes because it keeps the ordering, the dictionary is for checking duplicates - list.Add(new KeyValuePair(routeName, route)); - dictionary.Add(routeName, route); - - if (route is IPartialMatchRoute partialMatchRoute) - { - partialMatchRoutes.Add(partialMatchRoute); - } + AddCore(routeName, urlPattern, virtualPath: null, defaultValues, presenterFactory); } public void AddPartialMatchHandler(IPartialMatchRouteHandler handler) @@ -309,16 +309,21 @@ IEnumerator IEnumerable.GetEnumerator() return GetEnumerator(); } - private string CombinePath(string? prefix, string? appendedPath) + [return: NotNullIfNotNull(nameof(appendedPath))] + private string? CombinePath(string? prefix, string? appendedPath) { if (string.IsNullOrEmpty(prefix)) { - return appendedPath ?? ""; + return appendedPath; } - if (string.IsNullOrEmpty(appendedPath)) + if (appendedPath == null) + { + return null; + } + else if (appendedPath == string.Empty) { - return prefix!; + return prefix ?? string.Empty; } return $"{prefix}/{appendedPath}"; diff --git a/src/Framework/Framework/Routing/LocalizedDotvvmRoute.cs b/src/Framework/Framework/Routing/LocalizedDotvvmRoute.cs index be2904277..c0ec89b2e 100644 --- a/src/Framework/Framework/Routing/LocalizedDotvvmRoute.cs +++ b/src/Framework/Framework/Routing/LocalizedDotvvmRoute.cs @@ -1,13 +1,8 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; -using System.Net.WebSockets; -using System.Text; -using System.Threading; using DotVVM.Framework.Configuration; using DotVVM.Framework.Hosting; @@ -36,34 +31,18 @@ public sealed class LocalizedDotvvmRoute : RouteBase, IPartialMatchRoute public override IEnumerable> ParameterMetadata => GetRouteForCulture(CultureInfo.CurrentUICulture).ParameterMetadata; - public override string RouteName - { - get - { - return base.RouteName; - } - internal set - { - base.RouteName = value; - foreach (var route in localizedRoutes) - { - route.Value.RouteName = value; - } - } - } - /// /// Initializes a new instance of the class. /// - public LocalizedDotvvmRoute(string defaultLanguageUrl, LocalizedRouteUrl[] localizedUrls, string virtualPath, object? defaultValues, Func presenterFactory, DotvvmConfiguration configuration) - : base(defaultLanguageUrl, virtualPath, defaultValues) + public LocalizedDotvvmRoute(string defaultLanguageUrl, LocalizedRouteUrl[] localizedUrls, string? virtualPath, string name, object? defaultValues, Func presenterFactory, DotvvmConfiguration configuration) + : base(defaultLanguageUrl, virtualPath, name, defaultValues, presenterFactory) { if (!localizedUrls.Any()) { throw new ArgumentException("There must be at least one localized route URL!", nameof(localizedUrls)); } - var defaultRoute = new DotvvmRoute(defaultLanguageUrl, virtualPath, defaultValues, presenterFactory, configuration); + var defaultRoute = new DotvvmRoute(defaultLanguageUrl, virtualPath, name, defaultValues, presenterFactory, configuration); var sortedParameters = defaultRoute.ParameterMetadata .OrderBy(n => n.Key) @@ -71,7 +50,7 @@ public LocalizedDotvvmRoute(string defaultLanguageUrl, LocalizedRouteUrl[] local foreach (var localizedUrl in localizedUrls) { - var localizedRoute = new DotvvmRoute(localizedUrl.RouteUrl, virtualPath, defaultValues, presenterFactory, configuration); + var localizedRoute = new DotvvmRoute(localizedUrl.RouteUrl, virtualPath, name, defaultValues, presenterFactory, configuration); if (!localizedRoute.ParameterMetadata.OrderBy(n => n.Key) .SequenceEqual(sortedParameters)) { diff --git a/src/Framework/Framework/Routing/RouteBase.cs b/src/Framework/Framework/Routing/RouteBase.cs index 84ddcc3d3..a0ebe7d67 100644 --- a/src/Framework/Framework/Routing/RouteBase.cs +++ b/src/Framework/Framework/Routing/RouteBase.cs @@ -1,10 +1,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using DotVVM.Framework.Hosting; -using System.Reflection; -using System.Collections.ObjectModel; using DotVVM.Framework.Configuration; using System.Diagnostics.CodeAnalysis; @@ -12,6 +8,10 @@ namespace DotVVM.Framework.Routing { public abstract class RouteBase { + /// + /// Gets or sets a factory that provides an implementation of IDotvvmPresenter to handle the matching requests. + /// + public Func PresenterFactory { get; } /// /// Gets the URL pattern for the route. @@ -26,49 +26,42 @@ public abstract class RouteBase /// /// Gets key of route. /// - public virtual string RouteName { get; internal set; } + public string RouteName { get; } /// /// Gets the default values of the optional parameters. /// - public IDictionary DefaultValues => _defaultValues; - private IDictionary _defaultValues; + public IReadOnlyDictionary DefaultValues => _defaultValues; + private FreezableDictionary _defaultValues; /// /// Gets or sets the virtual path to the view. /// - public string VirtualPath - { - get => _virtualPath; - set { ThrowIfFrozen(); _virtualPath = value; } - } - private string _virtualPath; + public string? VirtualPath { get; } /// /// Initializes a new instance of the class. /// - public RouteBase(string url, string virtualPath, object? defaultValues = null) - : this(url, virtualPath, new Dictionary()) + public RouteBase(string url, string? virtualPath, string name, object? defaultValues, Func presenterFactory) + : this(url, virtualPath, name, new Dictionary(), presenterFactory) { - AddOrUpdateParameterCollection(DefaultValues, defaultValues); - } - - public RouteBase(string url, string virtualPath, string name, IDictionary? defaultValues) - : this(url, virtualPath, defaultValues) - { - this.RouteName = name; + AddOrUpdateParameterCollection(_defaultValues, defaultValues); } /// /// Initializes a new instance of the class. /// - public RouteBase(string url, string virtualPath, IDictionary? defaultValues = null) + public RouteBase(string url, string? virtualPath, string name, IDictionary? defaultValues, Func presenterFactory) { + if (string.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); if (url == null) throw new ArgumentNullException(nameof(url)); + RouteName = name; Url = url; - _virtualPath = virtualPath; + VirtualPath = virtualPath; + PresenterFactory = presenterFactory; if (defaultValues != null) { @@ -78,8 +71,6 @@ public RouteBase(string url, string virtualPath, IDictionary? d { _defaultValues = new FreezableDictionary(StringComparer.OrdinalIgnoreCase); } - - this.RouteName = null!; // practically, RouteName should not be null. unfortunately, this overload is used heavily and the RouteName is assigned by the Table.Add method :/ } @@ -109,7 +100,7 @@ public string BuildUrl(IDictionary currentRouteValues, IDiction if (newRouteValues == null) throw new ArgumentNullException(nameof(newRouteValues)); - var values = new Dictionary(DefaultValues, StringComparer.OrdinalIgnoreCase); + var values = new Dictionary(_defaultValues, StringComparer.OrdinalIgnoreCase); AddOrUpdateParameterCollection(values, currentRouteValues); AddOrUpdateParameterCollection(values, newRouteValues); @@ -124,7 +115,7 @@ public string BuildUrl(IDictionary currentRouteValues, object? if (currentRouteValues == null) throw new ArgumentNullException(nameof(currentRouteValues)); - var values = new Dictionary(DefaultValues, StringComparer.OrdinalIgnoreCase); + var values = new Dictionary(_defaultValues, StringComparer.OrdinalIgnoreCase); AddOrUpdateParameterCollection(values, currentRouteValues); AddOrUpdateParameterCollection(values, newRouteValues); @@ -136,7 +127,7 @@ public string BuildUrl(IDictionary currentRouteValues, object? /// public string BuildUrl(object? routeValues = null) { - var values = new Dictionary(DefaultValues, StringComparer.OrdinalIgnoreCase); + var values = new Dictionary(_defaultValues, StringComparer.OrdinalIgnoreCase); AddOrUpdateParameterCollection(values, routeValues); return BuildUrl(values); @@ -152,7 +143,7 @@ public string BuildUrl(IDictionary routeValues) routeValues = new Dictionary(); } - var values = new Dictionary(DefaultValues, StringComparer.OrdinalIgnoreCase); + var values = new Dictionary(_defaultValues, StringComparer.OrdinalIgnoreCase); AddOrUpdateParameterCollection(values, routeValues); return BuildUrlCore(values); @@ -203,11 +194,13 @@ public static void AddOrUpdateParameterCollection(IDictionary t } } - /// /// Returns a presenter that processes the request. /// - public abstract IDotvvmPresenter GetPresenter(IServiceProvider provider); + public virtual IDotvvmPresenter GetPresenter(IServiceProvider provider) => PresenterFactory(provider); + + public Dictionary CloneDefaultValues() => new Dictionary(_defaultValues, StringComparer.OrdinalIgnoreCase); + private bool isFrozen = false; @@ -219,7 +212,7 @@ private void ThrowIfFrozen() public void Freeze() { this.isFrozen = true; - FreezableDictionary.Freeze(ref this._defaultValues); + this._defaultValues.Freeze(); Freeze2(); } diff --git a/src/Framework/Framework/Routing/RouteHelper.cs b/src/Framework/Framework/Routing/RouteHelper.cs index 8b7ea265f..efafff423 100644 --- a/src/Framework/Framework/Routing/RouteHelper.cs +++ b/src/Framework/Framework/Routing/RouteHelper.cs @@ -32,7 +32,7 @@ public static void AutoDiscoverRoutes(this DotvvmRouteTable table, IRoutingStrat { foreach (var route in strategy.GetRoutes()) { - table.Add(route.RouteName, route); + table.Add(route); } } /// diff --git a/src/Framework/Framework/Routing/RouteTableGroup.cs b/src/Framework/Framework/Routing/RouteTableGroup.cs index a0619a8e8..22685d8a6 100644 --- a/src/Framework/Framework/Routing/RouteTableGroup.cs +++ b/src/Framework/Framework/Routing/RouteTableGroup.cs @@ -7,7 +7,7 @@ namespace DotVVM.Framework.Routing { public class RouteTableGroup { - public Action AddToParentRouteTable { get; private set; } + public Action AddToParentRouteTable { get; private set; } public Func? PresenterFactory { get; } @@ -16,7 +16,7 @@ public class RouteTableGroup public string UrlPrefix { get; private set; } public string VirtualPathPrefix { get; private set; } - public RouteTableGroup(string groupName, string routeNamePrefix, string urlPrefix, string virtualPathPrefix, Action addToParentRouteTable, Func? presenterFactory) + public RouteTableGroup(string groupName, string routeNamePrefix, string urlPrefix, string virtualPathPrefix, Action addToParentRouteTable, Func? presenterFactory) { GroupName = groupName; RouteNamePrefix = routeNamePrefix; diff --git a/src/Framework/Framework/Routing/RouteTableJsonConverter.cs b/src/Framework/Framework/Routing/RouteTableJsonConverter.cs index 43d3f252e..dc8596b32 100644 --- a/src/Framework/Framework/Routing/RouteTableJsonConverter.cs +++ b/src/Framework/Framework/Routing/RouteTableJsonConverter.cs @@ -17,25 +17,6 @@ public class RouteTableJsonConverter : JsonConverter public override DotvvmRouteTable Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { throw new NotImplementedException(); - // var rt = new DotvvmRouteTable(); - // if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException("Expected StartObject"); - // reader.Read(); - - // while (reader.TokenType == JsonTokenType.PropertyName) - // { - // var routeName = reader.GetString(); - // reader.Read(); - // var route = (JObject)prop.Value.NotNull(); - // try - // { - // rt.Add(prop.Key, route["url"].NotNull("route.url is required").Value(), (route["virtualPath"]?.Value()).NotNull("route.virtualPath is required"), route["defaultValues"]?.ToObject>()); - // } - // catch (Exception error) - // { - // rt.Add(prop.Key, new ErrorRoute(route["url"]?.Value(), route["virtualPath"]?.Value(), prop.Key, route["defaultValues"]?.ToObject>(), error)); - // } - // } - // return rt; } public override void Write(Utf8JsonWriter writer, DotvvmRouteTable value, JsonSerializerOptions options) @@ -60,33 +41,5 @@ public override void Write(Utf8JsonWriter writer, DotvvmRouteTable value, JsonSe } writer.WriteEndObject(); } - - sealed class ErrorRoute : RouteBase - { - private readonly Exception error; - - public ErrorRoute(string? url, string? virtualPath, string? name, IDictionary? defaultValues, Exception error) - : base(url ?? "", virtualPath ?? "", name ?? "", defaultValues) - { - this.error = error; - } - - public override IEnumerable ParameterNames { get; } = new string[0]; - - public override IEnumerable> ParameterMetadata { get; } = new KeyValuePair[0]; - - public override string UrlWithoutTypes => base.Url; - - public override IDotvvmPresenter GetPresenter(IServiceProvider provider) => throw new InvalidOperationException($"Could not create route {RouteName}", error); - - public override bool IsMatch(string url, [MaybeNullWhen(false)] out IDictionary values) => throw new InvalidOperationException($"Could not create route {RouteName}", error); - - protected internal override string BuildUrlCore(Dictionary values) => throw new InvalidOperationException($"Could not create route {RouteName}", error); - - protected override void Freeze2() - { - // no mutable properties in this class - } - } } } diff --git a/src/Framework/Testing/ControlTestHelper.cs b/src/Framework/Testing/ControlTestHelper.cs index 81e103ddc..13efffbd3 100644 --- a/src/Framework/Testing/ControlTestHelper.cs +++ b/src/Framework/Testing/ControlTestHelper.cs @@ -87,7 +87,7 @@ private TestDotvvmRequestContext PrepareRequest( var context = DotvvmTestHelper.CreateContext( Configuration, - route: new Framework.Routing.DotvvmRoute("testpage", fileName, null, _ => throw new Exception(), Configuration), + route: new Framework.Routing.DotvvmRoute("testpage", fileName, "testpage", null, _ => throw new Exception(), Configuration), requestType: postback is object ? DotvvmRequestType.Command : DotvvmRequestType.Navigate ); context.CsrfToken = null; diff --git a/src/Tests/Routing/DotvvmRouteTests.cs b/src/Tests/Routing/DotvvmRouteTests.cs index a6d7e6847..f21f83448 100644 --- a/src/Tests/Routing/DotvvmRouteTests.cs +++ b/src/Tests/Routing/DotvvmRouteTests.cs @@ -24,7 +24,7 @@ public class DotvvmRouteTests public void DotvvmRoute_IsMatch_RouteMustNotStartWithSlash() { Assert.ThrowsException(() => { - var route = new DotvvmRoute("/Test", null, null, null, configuration); + var route = new DotvvmRoute("/Test", null, "testpage", null, null, configuration); }); } @@ -33,14 +33,14 @@ public void DotvvmRoute_IsMatch_RouteMustNotStartWithSlash() public void DotvvmRoute_IsMatch_RouteMustNotEndWithSlash() { Assert.ThrowsException(() => { - var route = new DotvvmRoute("Test/", null, null, null, configuration); + var route = new DotvvmRoute("Test/", null, "testpage", null, null, configuration); }); } [TestMethod] public void DotvvmRoute_IsMatch_EmptyRouteMatchesEmptyUrl() { - var route = new DotvvmRoute("", null, null, null, configuration); + var route = new DotvvmRoute("", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("", out parameters); @@ -51,7 +51,7 @@ public void DotvvmRoute_IsMatch_EmptyRouteMatchesEmptyUrl() [TestMethod] public void DotvvmRoute_IsMatch_UrlWithoutParametersExactMatch() { - var route = new DotvvmRoute("Hello/Test/Page.txt", null, null, null, configuration); + var route = new DotvvmRoute("Hello/Test/Page.txt", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Hello/Test/Page.txt", out parameters); @@ -62,7 +62,7 @@ public void DotvvmRoute_IsMatch_UrlWithoutParametersExactMatch() [TestMethod] public void DotvvmRoute_IsMatch_UrlWithoutParametersNoMatch() { - var route = new DotvvmRoute("Hello/Test/Page.txt", null, null, null, configuration); + var route = new DotvvmRoute("Hello/Test/Page.txt", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Hello/Test/Page", out parameters); @@ -73,7 +73,7 @@ public void DotvvmRoute_IsMatch_UrlWithoutParametersNoMatch() [TestMethod] public void DotvvmRoute_IsMatch_UrlTwoParametersBothSpecified() { - var route = new DotvvmRoute("Article/{Id}/{Title}", null, new { Title = "test" }, null, configuration); + var route = new DotvvmRoute("Article/{Id}/{Title}", null, "testpage", new { Title = "test" }, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/15/Test-title", out parameters); @@ -87,7 +87,7 @@ public void DotvvmRoute_IsMatch_UrlTwoParametersBothSpecified() [TestMethod] public void DotvvmRoute_IsMatch_UrlTwoParametersOneSpecifiedOneDefault() { - var route = new DotvvmRoute("Article/{Id}/{Title}", null, new { Title = "test" }, null, configuration); + var route = new DotvvmRoute("Article/{Id}/{Title}", null, "testpage", new { Title = "test" }, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/15", out parameters); @@ -102,7 +102,7 @@ public void DotvvmRoute_IsMatch_UrlTwoParametersOneSpecifiedOneDefault() [TestMethod] public void DotvvmRoute_IsMatch_UrlTwoParametersBothRequired_NoMatchWhenOneSpecified() { - var route = new DotvvmRoute("Article/{Id}/{Title}", null, null, null, configuration); + var route = new DotvvmRoute("Article/{Id}/{Title}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/15", out parameters); @@ -113,7 +113,7 @@ public void DotvvmRoute_IsMatch_UrlTwoParametersBothRequired_NoMatchWhenOneSpeci [TestMethod] public void DotvvmRoute_IsMatch_UrlTwoParametersBothRequired_DifferentPart() { - var route = new DotvvmRoute("Article/id_{Id}/{Title}", null, null, null, configuration); + var route = new DotvvmRoute("Article/id_{Id}/{Title}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Articles/id_15", out parameters); @@ -124,7 +124,7 @@ public void DotvvmRoute_IsMatch_UrlTwoParametersBothRequired_DifferentPart() [TestMethod] public void DotvvmRoute_IsMatch_UrlOneParameterRequired_TwoSpecified() { - var route = new DotvvmRoute("Article/{Id}", null, null, null, configuration); + var route = new DotvvmRoute("Article/{Id}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/15/test", out parameters); @@ -135,7 +135,7 @@ public void DotvvmRoute_IsMatch_UrlOneParameterRequired_TwoSpecified() [TestMethod] public void DotvvmRoute_IsMatch_UrlTwoParametersBothRequired_BothSpecified() { - var route = new DotvvmRoute("Article/id_{Id}/{Title}", null, null, null, configuration); + var route = new DotvvmRoute("Article/id_{Id}/{Title}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/id_15/test", out parameters); @@ -149,7 +149,7 @@ public void DotvvmRoute_IsMatch_UrlTwoParametersBothRequired_BothSpecified() [TestMethod] public void DotvvmRoute_IsMatch_OneOptionalPrefixedParameter() { - var route = new DotvvmRoute("{Id?}/Article", null, null, null, configuration); + var route = new DotvvmRoute("{Id?}/Article", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article", out parameters); @@ -161,7 +161,7 @@ public void DotvvmRoute_IsMatch_OneOptionalPrefixedParameter() [TestMethod] public void DotvvmRoute_IsMatch_OneOptionalSuffixedParameter_WithConstraint() { - var route = new DotvvmRoute("Article/{Id?:int}", null, null, null, configuration); + var route = new DotvvmRoute("Article/{Id?:int}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article", out parameters); @@ -173,7 +173,7 @@ public void DotvvmRoute_IsMatch_OneOptionalSuffixedParameter_WithConstraint() [TestMethod] public void DotvvmRoute_IsMatch_OneOptionalSuffixedParameter_WithConstraint_SlashAtTheEnd() { - var route = new DotvvmRoute("Article/{Id?:int}", null, null, null, configuration); + var route = new DotvvmRoute("Article/{Id?:int}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/", out parameters); @@ -185,7 +185,7 @@ public void DotvvmRoute_IsMatch_OneOptionalSuffixedParameter_WithConstraint_Slas [TestMethod] public void DotvvmRoute_IsMatch_JustOneOptionalParameter() { - var route = new DotvvmRoute("{Id?}", null, null, null, configuration); + var route = new DotvvmRoute("{Id?}", null, "testpage", null, null, configuration); Assert.IsTrue(route.IsMatch("", out var params1)); Assert.AreEqual(0, params1.Count); @@ -198,7 +198,7 @@ public void DotvvmRoute_IsMatch_JustOneOptionalParameter() [TestMethod] public void DotvvmRoute_IsMatch_JustOneOptionalParameterWithConstraint() { - var route = new DotvvmRoute("{Id?:int}", null, null, null, configuration); + var route = new DotvvmRoute("{Id?:int}", null, "testpage", null, null, configuration); Assert.IsTrue(route.IsMatch("", out var params1)); Assert.AreEqual(0, params1.Count); @@ -213,7 +213,7 @@ public void DotvvmRoute_IsMatch_JustOneOptionalParameterWithConstraint() [TestMethod] public void DotvvmRoute_IsMatch_JustOneOptionalParameterWithConstraint_DefaultValue() { - var route = new DotvvmRoute("{Id?:int}", null, new { Id = 0 }, null, configuration); + var route = new DotvvmRoute("{Id?:int}", null, "testpage", new { Id = 0 }, null, configuration); Assert.IsTrue(route.IsMatch("", out var params1)); Assert.AreEqual(1, params1.Count); @@ -229,7 +229,7 @@ public void DotvvmRoute_IsMatch_JustOneOptionalParameterWithConstraint_DefaultVa [TestMethod] public void DotvvmRoute_IsMatch_OneOptionalParameter() { - var route = new DotvvmRoute("Article/{Id?}/edit", null, null, null, configuration); + var route = new DotvvmRoute("Article/{Id?}/edit", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/edit", out parameters); @@ -241,7 +241,7 @@ public void DotvvmRoute_IsMatch_OneOptionalParameter() [TestMethod] public void DotvvmRoute_IsMatch_OneOptionalParameter_DefaultValue() { - var route = new DotvvmRoute("Article/{Id?}/edit", null, new { Id = 0 }, null, configuration); + var route = new DotvvmRoute("Article/{Id?}/edit", null, "testpage", new { Id = 0 }, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/edit", out parameters); @@ -254,7 +254,7 @@ public void DotvvmRoute_IsMatch_OneOptionalParameter_DefaultValue() [TestMethod] public void DotvvmRoute_IsMatch_TwoParameters_OneOptional_Suffix() { - var route = new DotvvmRoute("Article/Test/{Id?}/{Id2}/suffix", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id?}/{Id2}/suffix", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/Test/5/suffix", out parameters); @@ -272,7 +272,7 @@ public void LocalizedDotvvmRoute_IsMatch_ExactCultureMatch() new LocalizedRouteUrl("cs", "cs"), new LocalizedRouteUrl("cs-CZ", "cs-CZ"), new LocalizedRouteUrl("en", "en") - }, "", null, _ => null, configuration); + }, "", "testpage", null, _ => null, configuration); var result = route.IsMatch("cs-CZ", out var parameters); Assert.IsTrue(result); @@ -287,7 +287,7 @@ public void LocalizedDotvvmRoute_IsMatch_TwoLetterCultureMatch() new LocalizedRouteUrl("cs", "cs"), new LocalizedRouteUrl("cs-CZ", "cs-CZ"), new LocalizedRouteUrl("en", "en") - }, "", null, _ => null, configuration); + }, "", "testpage", null, _ => null, configuration); var result = route.IsMatch("en", out var parameters); Assert.IsTrue(result); @@ -302,7 +302,7 @@ public void LocalizedDotvvmRoute_IsMatch_InvalidCultureMatch() new LocalizedRouteUrl("cs", "cs"), new LocalizedRouteUrl("cs-CZ", "cs-CZ"), new LocalizedRouteUrl("en", "en") - }, "", null, _ => null, configuration); + }, "", "testpage", null, _ => null, configuration); var result = route.IsMatch("cs", out var parameters); Assert.IsFalse(result); @@ -317,7 +317,7 @@ public void LocalizedDotvvmRoute_IsPartialMatch() new LocalizedRouteUrl("cs", "cs"), new LocalizedRouteUrl("cs-CZ", "cs-CZ"), new LocalizedRouteUrl("en", "en") - }, "", null, _ => null, configuration); + }, "", "testpage", null, _ => null, configuration); var result = route.IsPartialMatch("cs", out var matchedRoute, out var parameters); Assert.IsTrue(result); @@ -336,14 +336,14 @@ public void LocalizedDotvvmRoute_RouteConstraintChecks(string defaultRoute, stri Assert.ThrowsException(() => { var route = new LocalizedDotvvmRoute(defaultRoute, new[] { new LocalizedRouteUrl("en", localizedRoute) - }, "", null, _ => null, configuration); + }, "", "testpage", null, _ => null, configuration); }); } [TestMethod] public void DotvvmRoute_BuildUrl_UrlTwoParameters() { - var route = new DotvvmRoute("Article/id_{Id}/{Title}", null, null, null, configuration); + var route = new DotvvmRoute("Article/id_{Id}/{Title}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id = 15, Title = "Test" }); @@ -353,7 +353,7 @@ public void DotvvmRoute_BuildUrl_UrlTwoParameters() [TestMethod] public void DotvvmRoute_BuildUrl_Static_OnePart() { - var route = new DotvvmRoute("Article", null, null, null, configuration); + var route = new DotvvmRoute("Article", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); @@ -363,7 +363,7 @@ public void DotvvmRoute_BuildUrl_Static_OnePart() [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts() { - var route = new DotvvmRoute("Article/Test", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); @@ -373,7 +373,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts() [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts_OptionalParameter_NoValue() { - var route = new DotvvmRoute("Article/Test/{Id?}", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id?}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); @@ -384,7 +384,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts_OptionalParameter_NoValue() [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts_OptionalParameter_WithValue() { - var route = new DotvvmRoute("Article/Test/{Id?}", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id?}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id = 5 }); @@ -394,7 +394,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts_OptionalParameter_WithValue() [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_NoValue() { - var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id = 5 }); @@ -404,7 +404,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_NoVal [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_NoValue_Suffix() { - var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}/suffix", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}/suffix", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id = 5 }); @@ -415,7 +415,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_NoVal [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_WithValue() { - var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id = 5, Id2 = "aaa" }); @@ -427,7 +427,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_WithV [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_WithValue_Suffix() { - var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}/suffix", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id}/{Id2?}/suffix", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id = 5, Id2 = "aaa" }); @@ -439,7 +439,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_OneOptional_WithV [TestMethod] public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_FirstOptionalOptional_Suffix() { - var route = new DotvvmRoute("Article/Test/{Id?}/{Id2}/suffix", null, null, null, configuration); + var route = new DotvvmRoute("Article/Test/{Id?}/{Id2}/suffix", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id2 = "aaa" }); @@ -450,7 +450,7 @@ public void DotvvmRoute_BuildUrl_Static_TwoParts_TwoParameters_FirstOptionalOpti [TestMethod] public void DotvvmRoute_BuildUrl_CombineParameters_OneOptional() { - var route = new DotvvmRoute("Article/{Id?}", null, null, null, configuration); + var route = new DotvvmRoute("Article/{Id?}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new Dictionary() { { "Id", 5 } }, new { }); @@ -460,7 +460,7 @@ public void DotvvmRoute_BuildUrl_CombineParameters_OneOptional() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterOnly() { - var route = new DotvvmRoute("{Id?}", null, null, null, configuration); + var route = new DotvvmRoute("{Id?}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); @@ -470,7 +470,7 @@ public void DotvvmRoute_BuildUrl_ParameterOnly() [TestMethod] public void DotvvmRoute_BuildUrl_OptionalParameter() { - var route = new DotvvmRoute("myPage/{Id?}/edit", null, null, null, configuration); + var route = new DotvvmRoute("myPage/{Id?}/edit", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); var result2 = route.BuildUrl(new Dictionary { ["Id"] = null }); @@ -482,7 +482,7 @@ public void DotvvmRoute_BuildUrl_OptionalParameter() [TestMethod] public void DotvvmRoute_BuildUrl_OneOptionalPrefixedParameter() { - var route = new DotvvmRoute("{Id?}/Article", null, null, null, configuration); + var route = new DotvvmRoute("{Id?}/Article", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); var result2 = route.BuildUrl(new Dictionary { ["Id"] = 0 }); @@ -494,7 +494,7 @@ public void DotvvmRoute_BuildUrl_OneOptionalPrefixedParameter() [TestMethod] public void DotvvmRoute_BuildUrl_NullInParameter() { - var route = new DotvvmRoute("myPage/{Id}/edit", null, null, null, configuration); + var route = new DotvvmRoute("myPage/{Id}/edit", null, "testpage", null, null, configuration); var ex = Assert.ThrowsException(() => { route.BuildUrl(new Dictionary { ["Id"] = null }); @@ -505,7 +505,7 @@ public void DotvvmRoute_BuildUrl_NullInParameter() [TestMethod] public void DotvvmRoute_BuildUrl_NoParameter() { - var route = new DotvvmRoute("RR", null, null, null, configuration); + var route = new DotvvmRoute("RR", null, "testpage", null, null, configuration); var result = route.BuildUrl(null); @@ -517,7 +517,7 @@ public void DotvvmRoute_BuildUrl_InvariantCulture() { CultureUtils.RunWithCulture("cs-CZ", () => { - var route = new DotvvmRoute("RR-{p}", null, null, null, configuration); + var route = new DotvvmRoute("RR-{p}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { p = 1.1 }); @@ -529,7 +529,7 @@ public void DotvvmRoute_BuildUrl_InvariantCulture() public void DotvvmRoute_BuildUrl_UrlEncode() { CultureUtils.RunWithCulture("cs-CZ", () => { - var route = new DotvvmRoute("RR-{p}", null, null, null, configuration); + var route = new DotvvmRoute("RR-{p}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { p = 1.1}); @@ -542,7 +542,7 @@ public void DotvvmRoute_BuildUrl_UrlEncode() public void DotvvmRoute_BuildUrl_CustomPrimitiveType() { CultureUtils.RunWithCulture("cs-CZ", () => { - var route = new DotvvmRoute("Test/{Id}", null, null, null, configuration); + var route = new DotvvmRoute("Test/{Id}", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { Id = new DecimalNumber(123.4m) }) + UrlHelper.BuildUrlSuffix(null, new { Id = new DecimalNumber(555.5m) }); Assert.AreEqual("~/Test/123%2C4?Id=555%2C5", result); @@ -554,7 +554,7 @@ public void DotvvmRoute_BuildUrl_Invalid_UnclosedParameter() { Assert.ThrowsException(() => { - var route = new DotvvmRoute("{Id", null, null, null, configuration); + var route = new DotvvmRoute("{Id", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); }); @@ -566,7 +566,7 @@ public void DotvvmRoute_BuildUrl_Invalid_UnclosedParameterConstraint() { Assert.ThrowsException(() => { - var route = new DotvvmRoute("{Id:int", null, null, null, configuration); + var route = new DotvvmRoute("{Id:int", null, "testpage", null, null, configuration); var result = route.BuildUrl(new { }); }); @@ -575,7 +575,7 @@ public void DotvvmRoute_BuildUrl_Invalid_UnclosedParameterConstraint() [TestMethod] public void DotvvmRoute_BuildUrl_Parameter_UrlDecode() { - var route = new DotvvmRoute("Article/{Title}", null, null, null, configuration); + var route = new DotvvmRoute("Article/{Title}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/" + Uri.EscapeDataString("x a d # ? %%%%% | ://"), out parameters); @@ -588,7 +588,7 @@ public void DotvvmRoute_BuildUrl_Parameter_UrlDecode() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterConstraint_Int() { - var route = new DotvvmRoute("Article/id_{Id:int}/{Title}", null, null, null, configuration); + var route = new DotvvmRoute("Article/id_{Id:int}/{Title}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("Article/id_15/test", out parameters); @@ -601,7 +601,7 @@ public void DotvvmRoute_BuildUrl_ParameterConstraint_Int() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterConstraint_FloatingPoints() { - var route = new DotvvmRoute("R/{float:float}-{double:double}-{decimal:decimal}", null, null, null, configuration); + var route = new DotvvmRoute("R/{float:float}-{double:double}-{decimal:decimal}", null, "testpage", null, null, configuration); IDictionary parameters; var result = route.IsMatch("R/1.12-1.12-1.12", out parameters); @@ -616,7 +616,7 @@ public void DotvvmRoute_BuildUrl_ParameterConstraint_FloatingPoints() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterConstraint_Guid() { - var route = new DotvvmRoute("{guid1:guid}{guid2:guid}{guid3:guid}{guid4:guid}", null, null, null, configuration); + var route = new DotvvmRoute("{guid1:guid}{guid2:guid}{guid3:guid}{guid4:guid}", null, "testpage", null, null, configuration); var guids = new[] { Guid.NewGuid(), @@ -639,7 +639,7 @@ public void DotvvmRoute_BuildUrl_ParameterConstraint_Guid() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterConstraint_Max() { - var route = new DotvvmRoute("{p:max(100)}", null, null, null, configuration); + var route = new DotvvmRoute("{p:max(100)}", null, "testpage", null, null, configuration); IDictionary parameters; Assert.IsFalse(route.IsMatch("101", out parameters)); @@ -654,7 +654,7 @@ public void DotvvmRoute_BuildUrl_ParameterConstraint_Max() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterConstraint_Ranges() { - var route = new DotvvmRoute("{range:range(1, 100.1)}/{max:max(100)}/{min:min(-55)}/{negrange:range(-100, -12)}/{posint:posint}", null, null, null, configuration); + var route = new DotvvmRoute("{range:range(1, 100.1)}/{max:max(100)}/{min:min(-55)}/{negrange:range(-100, -12)}/{posint:posint}", null, "testpage", null, null, configuration); IDictionary parameters; Assert.IsTrue(route.IsMatch("50/0/0/-50/5", out parameters)); @@ -677,7 +677,7 @@ public void DotvvmRoute_BuildUrl_ParameterConstraint_Ranges() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterConstraint_Bool() { - var route = new DotvvmRoute("la{bool:bool}eheh", null, null, null, configuration); + var route = new DotvvmRoute("la{bool:bool}eheh", null, "testpage", null, null, configuration); IDictionary parameters; Assert.IsTrue(route.IsMatch("latrueeheh", out parameters)); @@ -690,7 +690,7 @@ public void DotvvmRoute_BuildUrl_ParameterConstraint_Bool() [TestMethod] public void DotvvmRoute_BuildUrl_ParameterConstraintAlpha() { - var route = new DotvvmRoute("la1{aplha:alpha}7huh", null, null, null, configuration); + var route = new DotvvmRoute("la1{aplha:alpha}7huh", null, "testpage", null, null, configuration); IDictionary parameters; Assert.IsTrue(route.IsMatch("la1lala7huh", out parameters)); @@ -703,7 +703,7 @@ public void DotvvmRoute_BuildUrl_ParameterConstraintAlpha() [TestMethod] public void DotvvmRoute_Performance() { - var route = new DotvvmRoute("Article/{name}@{domain}/{id:int}", null, null, null, configuration); + var route = new DotvvmRoute("Article/{name}@{domain}/{id:int}", null, "testpage", null, null, configuration); IDictionary parameters; Assert.IsFalse(route.IsMatch("Article/f" + new string('@', 2000) + "f/4f", out parameters)); } @@ -739,7 +739,7 @@ public void DotvvmRoute_PresenterType() [TestMethod] public void DotvvmRoute_RegexConstraint() { - var route = new DotvvmRoute("test/{Name:regex((aa|bb|cc))}", null, null, null, configuration); + var route = new DotvvmRoute("test/{Name:regex((aa|bb|cc))}", null, "testpage", null, null, configuration); Assert.IsTrue(route.IsMatch("test/aa", out var parameters)); Assert.IsTrue(route.IsMatch("test/bb", out parameters)); Assert.IsTrue(route.IsMatch("test/cc", out parameters)); @@ -749,7 +749,7 @@ public void DotvvmRoute_RegexConstraint() [TestMethod] public void DotvvmRoute_UrlWithoutTypes() { - string parse(string url) => new DotvvmRoute(url, null, null, null, configuration).UrlWithoutTypes; + string parse(string url) => new DotvvmRoute(url, null, "testpage", null, null, configuration).UrlWithoutTypes; Assert.AreEqual(parse("test/xx/12"), "test/xx/12"); Assert.AreEqual(parse("test/{Param}-{PaRAM2}"), "test/{param}-{param2}"); diff --git a/src/Tests/Routing/RouteSerializationTests.cs b/src/Tests/Routing/RouteSerializationTests.cs index b5caa0802..e9fb54eb0 100644 --- a/src/Tests/Routing/RouteSerializationTests.cs +++ b/src/Tests/Routing/RouteSerializationTests.cs @@ -27,9 +27,9 @@ public void RouteTable_Deserialization() config1.RouteTable.Add("route2", "url2/{int:posint}", "file1.dothtml", new { a = "ccc" }); // Add unknown constraint, simulate user defined constraint that is not known to the VS Extension - var r = new DotvvmRoute("url3", "file1.dothtml", new { }, provider => null, config1); + var r = new DotvvmRoute("url3", "file1.dothtml", "route3", new { }, provider => null, config1); typeof(RouteBase).GetProperty("Url").SetMethod.Invoke(r, new[] { "url3/{a:unsuppotedConstraint}" }); - config1.RouteTable.Add("route3", r); + config1.RouteTable.Add(r); var settings = VisualStudioHelper.GetSerializerOptions(); var config2 = JsonSerializer.Deserialize(JsonSerializer.Serialize(config1, settings), settings); diff --git a/src/Tests/Routing/RouteTableGroupTests.cs b/src/Tests/Routing/RouteTableGroupTests.cs index 32868ddf7..217ff522c 100644 --- a/src/Tests/Routing/RouteTableGroupTests.cs +++ b/src/Tests/Routing/RouteTableGroupTests.cs @@ -27,8 +27,8 @@ public void RouteTableGroup_UrlWithParameters() var group = table.GetGroup("Group"); var route = group.First(); - Assert.AreEqual(route.RouteName, "Group_Route"); - Assert.AreEqual(route.VirtualPath, "PathPrefix/route.dothtml"); + Assert.AreEqual("Group_Route", route.RouteName); + Assert.AreEqual("PathPrefix/route.dothtml", route.VirtualPath); Assert.IsTrue(route.IsMatch("UrlPrefix/5/Article/test", out var parameters)); Assert.AreEqual("5", parameters["Id"]); @@ -40,7 +40,7 @@ public void RouteTableGroup_EmptyRouteName() { var table = new DotvvmRouteTable(configuration); table.AddGroup("Group", "UrlPrefix/{Id}", null, opt => { - opt.Add("Default", "", null, null, null, null); + opt.Add("Default", "", "route.dothtml", null, null, null); }); var group = table.GetGroup("Group"); @@ -56,7 +56,7 @@ public void RouteTableGroup_DefaultValues() { var table = new DotvvmRouteTable(configuration); table.AddGroup("Group", "UrlPrefix/{Id}", null, opt => { - opt.Add("Route", "Article/{Title}", null, new { Title = "test" }, null, null); + opt.Add("Route", "Article/{Title}", "route.dothtml", new { Title = "test" }, null, null); }); var group = table.GetGroup("Group"); @@ -74,8 +74,8 @@ public void RouteTableGroup_MultipleRoutes() { var table = new DotvvmRouteTable(configuration); table.AddGroup("Group", "UrlPrefix/{Id}", null, opt => { - opt.Add("Route0", "Article0/{Title}", null, null, null, null); - opt.Add("Route1", "Article1/{Title}", null, null, null, null); + opt.Add("Route0", "Article0/{Title}", "route.dothtml", null, null, null); + opt.Add("Route1", "Article1/{Title}", "route.dothtml", null, null, null); }); var group = table.GetGroup("Group"); @@ -90,8 +90,8 @@ public void RouteTableGroup_MultipleRoutesWithParameters() { var table = new DotvvmRouteTable(configuration); table.AddGroup("Group", "UrlPrefix/{Id}", null, opt => { - opt.Add("Route0", "Article0/{Title}", null, null, null, null); - opt.Add("Route1", "Article1/{Title}", null, null, null, null); + opt.Add("Route0", "Article0/{Title}", "route.dothtml", null, null, null); + opt.Add("Route1", "Article1/{Title}", "route.dothtml", null, null, null); }); var group = table.GetGroup("Group"); @@ -110,11 +110,11 @@ public void RouteTableGroup_NestedGroups() table.AddGroup("Group1", "UrlPrefix1", null, opt1 => { opt1.AddGroup("Group2", "UrlPrefix2", null, opt2 => { opt2.AddGroup("Group3", "UrlPrefix3", null, opt3 => { - opt3.Add("Route3", "Article3", null, null, null, null); + opt3.Add("Route3", "Article3", "route.dothtml", null, null, null); }); - opt2.Add("Route2", "Article2", null, null, null, null); + opt2.Add("Route2", "Article2", "route.dothtml", null, null, null); }); - opt1.Add("Route1", "Article1", null, null, null, null); + opt1.Add("Route1", "Article1", "route.dothtml", null, null, null); }); var group = table.GetGroup("Group1"); @@ -181,9 +181,37 @@ public void RouteTableGroup_DefaultPresenterFactory() var table = new DotvvmRouteTable(configuration); table.AddGroup("Group", null, null, opt => { - opt.Add("Article", ""); + opt.Add("Article", "", ""); }, p => p.GetRequiredService()); Assert.IsInstanceOfType(table.First().GetPresenter(configuration.ServiceProvider), typeof(TestPresenter)); } + + [TestMethod] + public void RouteTableGroup_Redirections() + { + var table = new DotvvmRouteTable(configuration); + table.AddGroup("Group", "Prefix", "VirtualPathPrefix", opt => { + opt.AddUrlRedirection("Url", "", "redirect.dothtml"); + opt.AddRouteRedirection("Route", "RedirectRoute", "Group_Url"); + opt.Add("Normal", "Normal", "normal.dothtml"); + }); + + var group = table.GetGroup("Group"); + + var urlRedirection = group.ElementAt(0); + Assert.AreEqual("Group_Url", urlRedirection.RouteName); + Assert.AreEqual("Prefix", urlRedirection.Url); + Assert.IsNull(urlRedirection.VirtualPath); + + var routeRedirection = group.ElementAt(1); + Assert.AreEqual("Group_Route", routeRedirection.RouteName); + Assert.AreEqual("Prefix/RedirectRoute", routeRedirection.Url); + Assert.IsNull(routeRedirection.VirtualPath); + + var normalRoute = group.ElementAt(2); + Assert.AreEqual("Group_Normal", normalRoute.RouteName); + Assert.AreEqual("Prefix/Normal", normalRoute.Url); + Assert.AreEqual("VirtualPathPrefix/normal.dothtml", normalRoute.VirtualPath); + } } } diff --git a/src/Tests/Runtime/DotvvmControlRenderedHtmlTests.cs b/src/Tests/Runtime/DotvvmControlRenderedHtmlTests.cs index f23705aec..6ea0298f4 100644 --- a/src/Tests/Runtime/DotvvmControlRenderedHtmlTests.cs +++ b/src/Tests/Runtime/DotvvmControlRenderedHtmlTests.cs @@ -81,7 +81,7 @@ RouteLink createRouteLink() routeLink.SetValue(Internal.IsSpaPageProperty, true); configuration = DotvvmTestHelper.CreateConfiguration(); - configuration.RouteTable.Add("TestRoute", "TestRoute"); + configuration.RouteTable.Add("TestRoute", "TestRoute", ""); configuration.Freeze(); return routeLink; } From 36615b813e1f85ad91f4024f3ca60101682f8bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg=20=28RIGANTI=20s=2Er=2Eo=2E=29?= Date: Sun, 3 Nov 2024 16:53:12 +0100 Subject: [PATCH 3/4] Fixed nullability warnings --- src/Framework/Framework/Compilation/DotHtmlFileInfo.cs | 6 +++--- .../Framework/Compilation/Static/StaticViewCompiler.cs | 3 ++- .../Framework/Hosting/AggregateMarkupFileLoader.cs | 4 +++- src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs | 3 ++- src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs | 3 ++- src/Framework/Framework/Routing/RouteHelper.cs | 2 +- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs b/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs index 157d45256..84e9489e7 100644 --- a/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs +++ b/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs @@ -14,7 +14,7 @@ public sealed class DotHtmlFileInfo public ImmutableArray Warnings { get; internal set; } = ImmutableArray.Empty; /// Gets or sets the virtual path to the view. - public string VirtualPath { get; } + public string? VirtualPath { get; } public string? TagName { get; } public string? Namespace { get; } @@ -25,7 +25,7 @@ public sealed class DotHtmlFileInfo public ImmutableArray? DefaultValues { get; } public bool? HasParameters { get; } - public DotHtmlFileInfo(string virtualPath, string? tagName = null, string? nameSpace = null, string? assembly = null, string? tagPrefix = null, string? url = null, string? routeName = null, ImmutableArray? defaultValues = null, bool? hasParameters = null) + public DotHtmlFileInfo(string? virtualPath, string? tagName = null, string? nameSpace = null, string? assembly = null, string? tagPrefix = null, string? url = null, string? routeName = null, ImmutableArray? defaultValues = null, bool? hasParameters = null) { VirtualPath = virtualPath; Status = IsDothtmlFile(virtualPath) ? CompilationState.None : CompilationState.NonCompilable; @@ -40,7 +40,7 @@ public DotHtmlFileInfo(string virtualPath, string? tagName = null, string? nameS HasParameters = hasParameters; } - private static bool IsDothtmlFile(string virtualPath) + private static bool IsDothtmlFile(string? virtualPath) { return !string.IsNullOrWhiteSpace(virtualPath) && ( diff --git a/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs b/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs index c9a4fe739..7f360100f 100644 --- a/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs +++ b/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs @@ -10,6 +10,7 @@ using DotVVM.Framework.Configuration; using DotVVM.Framework.Hosting; using DotVVM.Framework.Security; +using DotVVM.Framework.Utils; using Microsoft.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -33,7 +34,7 @@ public static ImmutableArray CompileAll( diagnostics.AddRange(CompileNoThrow(configuration, markupControl!)); } - var views = configuration.RouteTable.Select(r => r.VirtualPath).ToImmutableArray(); + var views = configuration.RouteTable.Select(r => r.VirtualPath).WhereNotNull().ToImmutableArray(); foreach(var view in views) { diagnostics.AddRange(CompileNoThrow(configuration, view)); diff --git a/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs b/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs index 0c25033cb..af8f17b3c 100644 --- a/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs +++ b/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using DotVVM.Framework.Configuration; +using DotVVM.Framework.Utils; namespace DotVVM.Framework.Hosting { @@ -39,7 +40,8 @@ public AggregateMarkupFileLoader() /// public string GetMarkupFileVirtualPath(IDotvvmRequestContext context) { - return context.Route!.VirtualPath; + return context.Route!.VirtualPath + ?? throw new Exception($"The route {context.Route.RouteName} must have a non-null virtual path."); } } } diff --git a/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs b/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs index d97d2ce1d..0a2b44353 100644 --- a/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs +++ b/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs @@ -38,7 +38,8 @@ public class DefaultMarkupFileLoader : IMarkupFileLoader /// public string GetMarkupFileVirtualPath(IDotvvmRequestContext context) { - return context.Route!.VirtualPath; + return context.Route!.VirtualPath + ?? throw new Exception($"The route {context.Route.RouteName} must have a non-null virtual path."); } } } diff --git a/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs b/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs index c424c37c7..2a680c087 100644 --- a/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs +++ b/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs @@ -58,7 +58,8 @@ public class EmbeddedMarkupFileLoader : IMarkupFileLoader /// public string GetMarkupFileVirtualPath(IDotvvmRequestContext context) { - return context.Route!.VirtualPath; + return context.Route!.VirtualPath + ?? throw new Exception($"The route {context.Route.RouteName} must have a non-null virtual path."); } } } diff --git a/src/Framework/Framework/Routing/RouteHelper.cs b/src/Framework/Framework/Routing/RouteHelper.cs index efafff423..78761b036 100644 --- a/src/Framework/Framework/Routing/RouteHelper.cs +++ b/src/Framework/Framework/Routing/RouteHelper.cs @@ -52,7 +52,7 @@ public static void AssertConfigurationIsValid(this DotvvmConfiguration config) invalidRoutes.Add(new DotvvmConfigurationAssertResult(route, DotvvmConfigurationAssertReason.MissingRouteName)); } - var content = loader.GetMarkup(config, route.VirtualPath); + var content = loader.GetMarkup(config, route.VirtualPath!); if (content == null) { invalidRoutes.Add(new DotvvmConfigurationAssertResult(route, DotvvmConfigurationAssertReason.MissingFile)); From 4a53635534296e6ff54ccee236162d59393e7eec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Herceg=20=28RIGANTI=20s=2Er=2Eo=2E=29?= Date: Sun, 3 Nov 2024 16:53:12 +0100 Subject: [PATCH 4/4] Fixed nullability warnings --- src/Framework/Framework/Compilation/DotHtmlFileInfo.cs | 6 +++--- .../Framework/Compilation/DotvvmViewCompilationService.cs | 6 +++--- .../Framework/Compilation/Static/StaticViewCompiler.cs | 3 ++- .../Framework/Hosting/AggregateMarkupFileLoader.cs | 4 +++- src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs | 3 ++- src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs | 3 ++- src/Framework/Framework/Routing/RouteHelper.cs | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs b/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs index 157d45256..84e9489e7 100644 --- a/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs +++ b/src/Framework/Framework/Compilation/DotHtmlFileInfo.cs @@ -14,7 +14,7 @@ public sealed class DotHtmlFileInfo public ImmutableArray Warnings { get; internal set; } = ImmutableArray.Empty; /// Gets or sets the virtual path to the view. - public string VirtualPath { get; } + public string? VirtualPath { get; } public string? TagName { get; } public string? Namespace { get; } @@ -25,7 +25,7 @@ public sealed class DotHtmlFileInfo public ImmutableArray? DefaultValues { get; } public bool? HasParameters { get; } - public DotHtmlFileInfo(string virtualPath, string? tagName = null, string? nameSpace = null, string? assembly = null, string? tagPrefix = null, string? url = null, string? routeName = null, ImmutableArray? defaultValues = null, bool? hasParameters = null) + public DotHtmlFileInfo(string? virtualPath, string? tagName = null, string? nameSpace = null, string? assembly = null, string? tagPrefix = null, string? url = null, string? routeName = null, ImmutableArray? defaultValues = null, bool? hasParameters = null) { VirtualPath = virtualPath; Status = IsDothtmlFile(virtualPath) ? CompilationState.None : CompilationState.NonCompilable; @@ -40,7 +40,7 @@ public DotHtmlFileInfo(string virtualPath, string? tagName = null, string? nameS HasParameters = hasParameters; } - private static bool IsDothtmlFile(string virtualPath) + private static bool IsDothtmlFile(string? virtualPath) { return !string.IsNullOrWhiteSpace(virtualPath) && ( diff --git a/src/Framework/Framework/Compilation/DotvvmViewCompilationService.cs b/src/Framework/Framework/Compilation/DotvvmViewCompilationService.cs index 6d633f2c8..f5fd8411e 100644 --- a/src/Framework/Framework/Compilation/DotvvmViewCompilationService.cs +++ b/src/Framework/Framework/Compilation/DotvvmViewCompilationService.cs @@ -122,7 +122,7 @@ public async Task CompileAll(bool buildInParallel = true, bool forceRecomp var compilationTaskFactory = (DotHtmlFileInfo t) => () => { BuildView(t, forceRecompile, out var masterPage); if (masterPage != null && masterPage.Status == CompilationState.None) - discoveredMasterPages.TryAdd(masterPage.VirtualPath, masterPage); + discoveredMasterPages.TryAdd(masterPage.VirtualPath!, masterPage); }; var compileTasks = filesToCompile.Select(compilationTaskFactory).ToArray(); @@ -185,9 +185,9 @@ public bool BuildView(DotHtmlFileInfo file, bool forceRecompile, out DotHtmlFile { if (forceRecompile) // TODO: next major version - add method to interface - (controlBuilderFactory as DefaultControlBuilderFactory)?.InvalidateCache(file.VirtualPath); + (controlBuilderFactory as DefaultControlBuilderFactory)?.InvalidateCache(file.VirtualPath!); - var pageBuilder = controlBuilderFactory.GetControlBuilder(file.VirtualPath); + var pageBuilder = controlBuilderFactory.GetControlBuilder(file.VirtualPath!); using var scopedServices = dotvvmConfiguration.ServiceProvider.CreateScope(); // dependencies that are configured as scoped cannot be resolved from root service provider scopedServices.ServiceProvider.GetRequiredService().Context = new ViewCompilationFakeRequestContext(scopedServices.ServiceProvider); diff --git a/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs b/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs index c9a4fe739..7f360100f 100644 --- a/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs +++ b/src/Framework/Framework/Compilation/Static/StaticViewCompiler.cs @@ -10,6 +10,7 @@ using DotVVM.Framework.Configuration; using DotVVM.Framework.Hosting; using DotVVM.Framework.Security; +using DotVVM.Framework.Utils; using Microsoft.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -33,7 +34,7 @@ public static ImmutableArray CompileAll( diagnostics.AddRange(CompileNoThrow(configuration, markupControl!)); } - var views = configuration.RouteTable.Select(r => r.VirtualPath).ToImmutableArray(); + var views = configuration.RouteTable.Select(r => r.VirtualPath).WhereNotNull().ToImmutableArray(); foreach(var view in views) { diagnostics.AddRange(CompileNoThrow(configuration, view)); diff --git a/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs b/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs index 0c25033cb..af8f17b3c 100644 --- a/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs +++ b/src/Framework/Framework/Hosting/AggregateMarkupFileLoader.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using DotVVM.Framework.Configuration; +using DotVVM.Framework.Utils; namespace DotVVM.Framework.Hosting { @@ -39,7 +40,8 @@ public AggregateMarkupFileLoader() /// public string GetMarkupFileVirtualPath(IDotvvmRequestContext context) { - return context.Route!.VirtualPath; + return context.Route!.VirtualPath + ?? throw new Exception($"The route {context.Route.RouteName} must have a non-null virtual path."); } } } diff --git a/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs b/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs index d97d2ce1d..0a2b44353 100644 --- a/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs +++ b/src/Framework/Framework/Hosting/DefaultMarkupFileLoader.cs @@ -38,7 +38,8 @@ public class DefaultMarkupFileLoader : IMarkupFileLoader /// public string GetMarkupFileVirtualPath(IDotvvmRequestContext context) { - return context.Route!.VirtualPath; + return context.Route!.VirtualPath + ?? throw new Exception($"The route {context.Route.RouteName} must have a non-null virtual path."); } } } diff --git a/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs b/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs index c424c37c7..2a680c087 100644 --- a/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs +++ b/src/Framework/Framework/Hosting/EmbeddedMarkupFileLoader.cs @@ -58,7 +58,8 @@ public class EmbeddedMarkupFileLoader : IMarkupFileLoader /// public string GetMarkupFileVirtualPath(IDotvvmRequestContext context) { - return context.Route!.VirtualPath; + return context.Route!.VirtualPath + ?? throw new Exception($"The route {context.Route.RouteName} must have a non-null virtual path."); } } } diff --git a/src/Framework/Framework/Routing/RouteHelper.cs b/src/Framework/Framework/Routing/RouteHelper.cs index efafff423..78761b036 100644 --- a/src/Framework/Framework/Routing/RouteHelper.cs +++ b/src/Framework/Framework/Routing/RouteHelper.cs @@ -52,7 +52,7 @@ public static void AssertConfigurationIsValid(this DotvvmConfiguration config) invalidRoutes.Add(new DotvvmConfigurationAssertResult(route, DotvvmConfigurationAssertReason.MissingRouteName)); } - var content = loader.GetMarkup(config, route.VirtualPath); + var content = loader.GetMarkup(config, route.VirtualPath!); if (content == null) { invalidRoutes.Add(new DotvvmConfigurationAssertResult(route, DotvvmConfigurationAssertReason.MissingFile));