| File: Routing.cs | Web Access |
| Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls) |
#nullable disable using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; namespace Microsoft.Maui.Controls { /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="Type[@FullName='Microsoft.Maui.Controls.Routing']/Docs/*" /> public static class Routing { static int s_routeCount = 0; static Dictionary<string, RouteFactory> s_routes = new(StringComparer.Ordinal); static Dictionary<string, Page> s_implicitPageRoutes = new(StringComparer.Ordinal); static HashSet<string> s_routeKeys; const string ImplicitPrefix = "IMPL_"; const string DefaultPrefix = "D_FAULT_"; internal const string PathSeparator = "/"; // We only need these while a navigation is happening internal static void ClearImplicitPageRoutes() { s_implicitPageRoutes.Clear(); s_routeKeys = null; } internal static void RegisterImplicitPageRoute(Page page) { var route = GetRoute(page); if (!IsUserDefined(route)) { s_implicitPageRoutes[route] = page; s_routeKeys = null; } } // Shell works much better if the entire nav stack can be represented by a string // If the users pushes pages without using routes we want these page keys tracked internal static void RegisterImplicitPageRoutes(Shell shell) { foreach (var item in shell.Items) { foreach (var section in item.Items) { var navigationStackCount = section.Navigation.NavigationStack.Count; for (int i = 1; i < navigationStackCount; i++) { RegisterImplicitPageRoute(section.Navigation.NavigationStack[i]); } var navigationModalStackCount = section.Navigation.ModalStack.Count; for (int i = 0; i < navigationModalStackCount; i++) { var page = section.Navigation.ModalStack[i]; RegisterImplicitPageRoute(page); if (page is INavigationPageController np) { foreach (var npPages in np.Pages) { RegisterImplicitPageRoute(npPages); } } } } } } internal static string GenerateImplicitRoute(string source) { if (IsImplicit(source)) return source; return String.Concat(ImplicitPrefix, source); } internal static bool IsImplicit(string source) { return source.StartsWith(ImplicitPrefix, StringComparison.Ordinal); } internal static bool IsImplicit(BindableObject source) { return IsImplicit(GetRoute(source)); } internal static bool IsDefault(string source) { return source.StartsWith(DefaultPrefix, StringComparison.Ordinal); } internal static bool IsDefault(BindableObject source) { return IsDefault(GetRoute(source)); } internal static bool IsUserDefined(BindableObject source) { if (source == null) return false; return IsUserDefined(GetRoute(source)); } internal static bool IsUserDefined(string route) { if (route == null) return false; return !(IsDefault(route) || IsImplicit(route)); } internal static void Clear() { s_implicitPageRoutes.Clear(); s_routes.Clear(); s_routeKeys = null; } /// <summary>Bindable property for attached property <c>Route</c>.</summary> public static readonly BindableProperty RouteProperty = CreateRouteProperty(); [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2111:ReflectionToDynamicallyAccessedMembers", Justification = "The CreateAttached method has a DynamicallyAccessedMembers annotation for all public methods" + "on the declaring type. This includes the Routing.RegisterRoute(string, Type) method which also has a " + "DynamicallyAccessedMembers annotation and the trimmer can't guarantee the availability of the requirements" + "of the method. `BindableProperty` only needs methods starting with `Get`, so `RegisterRoute` is never accessed via reflection.")] private static BindableProperty CreateRouteProperty() => BindableProperty.CreateAttached("Route", typeof(string), typeof(Routing), null, defaultValueCreator: CreateDefaultRoute); static object CreateDefaultRoute(BindableObject bindable) { return $"{DefaultPrefix}{bindable.GetType().Name}{++s_routeCount}"; } internal static HashSet<string> GetRouteKeys() { var keys = s_routeKeys; if (keys != null) return keys; keys = new HashSet<string>(StringComparer.Ordinal); foreach (var key in s_routes.Keys) { keys.Add(ShellUriHandler.FormatUri(key)); } foreach (var key in s_implicitPageRoutes.Keys) { keys.Add(ShellUriHandler.FormatUri(key)); } return s_routeKeys = keys; } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='GetOrCreateContent']/Docs/*" /> #pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) public static Element GetOrCreateContent(string route, IServiceProvider services = null) #pragma warning restore CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do) { Element result = null; if (s_implicitPageRoutes.TryGetValue(route, out var page)) { return page; } if (s_routes.TryGetValue(route, out var content)) result = content.GetOrCreate(services); if (result != null) SetRoute(result, route); return result; } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='GetRoute']/Docs/*" /> public static string GetRoute(BindableObject obj) { return (string)obj.GetValue(RouteProperty); } internal static string GetRoutePathIfNotImplicit(Element obj) { var source = GetRoute(obj); if (IsImplicit(source)) return String.Empty; return $"{source}/"; } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='FormatRoute'][1]/Docs/*" /> public static string FormatRoute(List<string> segments) { var route = FormatRoute(String.Join(PathSeparator, segments)); return route; } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='FormatRoute'][2]/Docs/*" /> public static string FormatRoute(string route) { return route; } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='RegisterRoute'][2]/Docs/*" /> public static void RegisterRoute(string route, RouteFactory factory) { if (!String.IsNullOrWhiteSpace(route)) route = FormatRoute(route); ValidateRoute(route, factory); s_routes[route] = factory; s_routeKeys = null; } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='UnRegisterRoute']/Docs/*" /> public static void UnRegisterRoute(string route) { if (s_routes.Remove(route)) { s_routeKeys = null; } } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='RegisterRoute'][1]/Docs/*" /> public static void RegisterRoute( string route, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) { RegisterRoute(route, new TypeRouteFactory(type)); } /// <include file="../../docs/Microsoft.Maui.Controls/Routing.xml" path="//Member[@MemberName='SetRoute']/Docs/*" /> public static void SetRoute(Element obj, string value) { obj.SetValue(RouteProperty, value); } static void ValidateRoute(string route, RouteFactory routeFactory) { if (string.IsNullOrWhiteSpace(route)) throw new ArgumentNullException(nameof(route), "Route cannot be an empty string"); routeFactory = routeFactory ?? throw new ArgumentNullException(nameof(routeFactory), "Route Factory cannot be null"); var uri = new Uri(route, UriKind.RelativeOrAbsolute); var parts = uri.OriginalString.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); foreach (var part in parts) { if (IsImplicit(part)) throw new ArgumentException($"Route contains invalid characters in \"{part}\""); } RouteFactory existingRegistration = null; if (s_routes.TryGetValue(route, out existingRegistration) && !existingRegistration.Equals(routeFactory)) throw new ArgumentException($"Duplicated Route: \"{route}\""); } class TypeRouteFactory : RouteFactory { [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] readonly Type _type; public TypeRouteFactory( [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) { _type = type; } public override Element GetOrCreate() { return (Element)Activator.CreateInstance(_type); } public override Element GetOrCreate(IServiceProvider services) { if (services != null) { return (Element)Extensions.DependencyInjection.ActivatorUtilities.GetServiceOrCreateInstance(services, _type); } return (Element)Activator.CreateInstance(_type); } public override bool Equals(object obj) { if ((obj is TypeRouteFactory typeRouteFactory)) return typeRouteFactory._type == _type; return false; } public override int GetHashCode() { return _type.GetHashCode(); } } } }