diff --git a/Directory.Build.props b/Directory.Build.props index f7d8373..1bfc993 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -5,6 +5,7 @@ Dan Siegel https://github.com/dansiegel/Prism.Maui LICENSE + ReadMe.md prism-logo.png false $(MSBuildThisFileDirectory)Artifacts @@ -27,6 +28,10 @@ + (); - //regionManager.RegisterViewWithRegion("ContentRegion", "RegionViewA"); + var regionManager = containerProvider.Resolve(); + regionManager.RegisterViewWithRegion("ContentRegion", "RegionViewA"); } public void RegisterTypes(IContainerRegistry containerRegistry) diff --git a/sample/MauiRegionsModule/ViewModels/ContentRegionPageViewModel.cs b/sample/MauiRegionsModule/ViewModels/ContentRegionPageViewModel.cs index 114b8ae..9bf3225 100644 --- a/sample/MauiRegionsModule/ViewModels/ContentRegionPageViewModel.cs +++ b/sample/MauiRegionsModule/ViewModels/ContentRegionPageViewModel.cs @@ -13,7 +13,7 @@ public ContentRegionPageViewModel(IRegionManager regionManager) public void Initialize(INavigationParameters parameters) { - _regionManager.RequestNavigate("ContentRegion", "RegionViewA"); + //_regionManager.RequestNavigate("ContentRegion", "RegionViewA"); } public DelegateCommand NavigateCommand { get; } diff --git a/sample/MauiRegionsModule/ViewModels/RegionViewModelBase.cs b/sample/MauiRegionsModule/ViewModels/RegionViewModelBase.cs index 2f36cd8..b602875 100644 --- a/sample/MauiRegionsModule/ViewModels/RegionViewModelBase.cs +++ b/sample/MauiRegionsModule/ViewModels/RegionViewModelBase.cs @@ -2,7 +2,7 @@ namespace MauiRegionsModule.ViewModels; -public abstract class RegionViewModelBase : BindableBase, IRegionAware +public abstract class RegionViewModelBase : BindableBase, IRegionAware, IPageLifecycleAware { protected string Name => GetType().Name.Replace("ViewModel", string.Empty); protected INavigationService _navigationService { get; } @@ -47,4 +47,13 @@ public void OnNavigatedTo(INavigationContext navigationContext) _regionNavigation = navigationContext.NavigationService; ViewCount = navigationContext.NavigationService.Region.Views.Count(); } + + public void OnAppearing() + { + RaisePropertyChanged(nameof(PageName)); + } + + public void OnDisappearing() + { + } } diff --git a/sample/PrismMauiDemo/MauiProgram.cs b/sample/PrismMauiDemo/MauiProgram.cs index a26359c..d4c8bd3 100644 --- a/sample/PrismMauiDemo/MauiProgram.cs +++ b/sample/PrismMauiDemo/MauiProgram.cs @@ -34,6 +34,9 @@ public static MauiApp CreateMauiApp() var status = x.Cancelled ? "Cancelled" : x.Result.Success ? "Success" : "Failed"; Console.WriteLine($"Result: {status}"); + + if (status == "Failed" && !string.IsNullOrEmpty(x.Result?.Exception?.Message)) + Console.Error.WriteLine(x.Result.Exception.Message); })) .OnAppStart(navigationService => navigationService.CreateBuilder() .AddNavigationSegment() diff --git a/src/Prism.DryIoc.Shared/DryIocContainerExtension.cs b/src/Prism.DryIoc.Shared/DryIocContainerExtension.cs index b78d83c..f6a851c 100644 --- a/src/Prism.DryIoc.Shared/DryIocContainerExtension.cs +++ b/src/Prism.DryIoc.Shared/DryIocContainerExtension.cs @@ -295,7 +295,11 @@ public object Resolve(Type type, params (Type Type, object Instance)[] parameter try { var container = _currentScope?.Resolver ?? Instance; - return container.Resolve(type, args: parameters.Select(p => p.Instance).ToArray()); + var args = parameters.Where(x => x.Instance is not IContainerProvider) + .Select(x => x.Instance) + .ToList(); + args.Add(this); + return container.Resolve(type, args: args.ToArray()); } catch (Exception ex) { @@ -315,7 +319,11 @@ public object Resolve(Type type, string name, params (Type Type, object Instance try { var container = _currentScope?.Resolver ?? Instance; - return container.Resolve(type, name, args: parameters.Select(p => p.Instance).ToArray()); + var args = parameters.Where(x => x.Instance is not IContainerProvider) + .Select(x => x.Instance) + .ToList(); + args.Add(this); + return container.Resolve(type, name, args: args.ToArray()); } catch (Exception ex) { @@ -391,7 +399,7 @@ public DryIocScopedProvider(IResolverContext resolver) public IResolverContext Resolver { get; private set; } public IScopedProvider CurrentScope => this; - public IScopedProvider CreateScope() => this; + public IScopedProvider CreateScope() => new DryIocScopedProvider(Resolver.OpenScope()); public void Dispose() { @@ -409,7 +417,11 @@ public object Resolve(Type type, params (Type Type, object Instance)[] parameter { try { - return Resolver.Resolve(type, args: parameters.Select(p => p.Instance).ToArray()); + var args = parameters.Where(x => x.Instance is not IContainerProvider) + .Select(x => x.Instance) + .ToList(); + args.Add(this); + return Resolver.Resolve(type, args: args.ToArray()); } catch (Exception ex) { @@ -421,7 +433,11 @@ public object Resolve(Type type, string name, params (Type Type, object Instance { try { - return Resolver.Resolve(type, name, args: parameters.Select(p => p.Instance).ToArray()); + var args = parameters.Where(x => x.Instance is not IContainerProvider) + .Select(x => x.Instance) + .ToList(); + args.Add(this); + return Resolver.Resolve(type, name, args: args.ToArray()); } catch (Exception ex) { diff --git a/src/Prism.Maui/Behaviors/DelayedRegionCreationCallbackBehavior.cs b/src/Prism.Maui/Behaviors/DelayedRegionCreationCallbackBehavior.cs index b8797ac..8a90e53 100644 --- a/src/Prism.Maui/Behaviors/DelayedRegionCreationCallbackBehavior.cs +++ b/src/Prism.Maui/Behaviors/DelayedRegionCreationCallbackBehavior.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using Prism.Extensions; +using Prism.Navigation.Xaml; namespace Prism.Behaviors; @@ -40,10 +41,10 @@ private void OnParentChanged(object sender, EventArgs e) private void PagePropertyChanged(object sender, PropertyChangedEventArgs e) { - if (sender is not Page page) + if (sender is not Page page || e.PropertyName != Navigation.Xaml.Navigation.PrismContainerProvider) return; - var container = page.GetValue(Navigation.Xaml.Navigation.NavigationScopeProperty); + var container = page.GetContainerProvider(); if(container is not null) { diff --git a/src/Prism.Maui/Behaviors/PageScopeBehavior.cs b/src/Prism.Maui/Behaviors/PageScopeBehavior.cs index 5c851dc..4fe1313 100644 --- a/src/Prism.Maui/Behaviors/PageScopeBehavior.cs +++ b/src/Prism.Maui/Behaviors/PageScopeBehavior.cs @@ -1,4 +1,6 @@ -namespace Prism.Behaviors; +using Prism.Navigation.Xaml; + +namespace Prism.Behaviors; /// /// Controls the Page container Scope @@ -16,6 +18,6 @@ protected override void OnDetachingFrom(Page page) { base.OnDetachingFrom(page); // This forces the Attached Property to get cleaned up. - page.SetValue(Navigation.Xaml.Navigation.NavigationScopeProperty, null); + page.SetContainerProvider(null); } } diff --git a/src/Prism.Maui/Behaviors/RegionCleanupBehavior.cs b/src/Prism.Maui/Behaviors/RegionCleanupBehavior.cs index 6eda320..0e83df7 100644 --- a/src/Prism.Maui/Behaviors/RegionCleanupBehavior.cs +++ b/src/Prism.Maui/Behaviors/RegionCleanupBehavior.cs @@ -1,4 +1,5 @@ using Prism.Ioc; +using Prism.Navigation.Xaml; using Prism.Regions; namespace Prism.Behaviors; @@ -8,9 +9,9 @@ internal class RegionCleanupBehavior : BehaviorBase private WeakReference _regionReference; public RegionCleanupBehavior(IRegion region) -{ + { _regionReference = new WeakReference(region); -} + } public IRegion Region => _regionReference.TryGetTarget(out var target) ? target : null; @@ -18,7 +19,7 @@ protected override void OnDetachingFrom(Page bindable) { if (Region != null) { - var container = bindable.GetValue(Navigation.Xaml.Navigation.NavigationScopeProperty) as IContainerProvider; + var container = bindable.GetContainerProvider(); var manager = Region.RegionManager ?? container.Resolve(); if (manager.Regions.ContainsRegionWithName(Region.Name)) { diff --git a/src/Prism.Maui/Common/IRegistryAware.cs b/src/Prism.Maui/Common/IRegistryAware.cs new file mode 100644 index 0000000..4639126 --- /dev/null +++ b/src/Prism.Maui/Common/IRegistryAware.cs @@ -0,0 +1,8 @@ +using Prism.Mvvm; + +namespace Prism.Common; + +public interface IRegistryAware +{ + IViewRegistry Registry { get; } +} \ No newline at end of file diff --git a/src/Prism.Maui/Common/MvvmHelpers.cs b/src/Prism.Maui/Common/MvvmHelpers.cs index 0726040..a7e73d2 100644 --- a/src/Prism.Maui/Common/MvvmHelpers.cs +++ b/src/Prism.Maui/Common/MvvmHelpers.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Reflection; using Prism.Navigation; +using Prism.Navigation.Xaml; using Prism.Regions.Navigation; using NavigationMode = Prism.Navigation.NavigationMode; @@ -19,6 +20,13 @@ public static void InvokeViewAndViewModelAction(object view, Action action { action(viewModelAsT); } + + if(view is Page page) + { + var children = page.GetChildViews(); + foreach (var child in children) + InvokeViewAndViewModelAction(child, action); + } } public static async Task InvokeViewAndViewModelActionAsync(object view, Func action) where T : class @@ -32,6 +40,13 @@ public static async Task InvokeViewAndViewModelActionAsync(object view, Func< { await action(viewModelAsT); } + + if (view is Page page) + { + var children = page.GetChildViews(); + foreach (var child in children) + await InvokeViewAndViewModelActionAsync(child, action); + } } public static void DestroyPage(IView view) @@ -104,15 +119,9 @@ public static T GetImplementerFromViewOrViewModel(object view) public static bool IsNavigationTarget(object view, INavigationContext navigationContext) { - if (view is IRegionAware viewAsRegionAware) - { - return viewAsRegionAware.IsNavigationTarget(navigationContext); - } - - if (view is BindableObject bindable && bindable.BindingContext is IRegionAware vmAsRegionAware) - { - return vmAsRegionAware.IsNavigationTarget(navigationContext); - } + var implementor = GetImplementerFromViewOrViewModel(view); + if (implementor is not null) + return implementor.IsNavigationTarget(navigationContext); var uri = navigationContext.Uri; if (!uri.IsAbsoluteUri) diff --git a/src/Prism.Maui/Navigation/ViewRegistration.cs b/src/Prism.Maui/Common/ViewRegistration.cs similarity index 69% rename from src/Prism.Maui/Navigation/ViewRegistration.cs rename to src/Prism.Maui/Common/ViewRegistration.cs index 5e277de..f59de8a 100644 --- a/src/Prism.Maui/Navigation/ViewRegistration.cs +++ b/src/Prism.Maui/Common/ViewRegistration.cs @@ -1,7 +1,8 @@ -namespace Prism.Navigation; +namespace Prism.Common; public record ViewRegistration { + public ViewType Type { get; init; } public Type View { get; init; } public Type ViewModel { get; init; } public string Name { get; init; } diff --git a/src/Prism.Maui/Common/ViewType.cs b/src/Prism.Maui/Common/ViewType.cs new file mode 100644 index 0000000..a8a68a5 --- /dev/null +++ b/src/Prism.Maui/Common/ViewType.cs @@ -0,0 +1,9 @@ +namespace Prism.Common; + +public enum ViewType +{ + Unknown, + Page, + Region, + Dialog, +} diff --git a/src/Prism.Maui/Ioc/IResolverOverridesHelper.cs b/src/Prism.Maui/Ioc/IResolverOverridesHelper.cs deleted file mode 100644 index 0741441..0000000 --- a/src/Prism.Maui/Ioc/IResolverOverridesHelper.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Prism.Ioc; - -/// -/// Provides a helper interface for Regions to be able to inject the current Region -/// -public interface IResolverOverridesHelper -{ - IEnumerable<(Type Type, object Instance)> GetOverrides(); -} \ No newline at end of file diff --git a/src/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs b/src/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs index a098dbe..e0800f4 100644 --- a/src/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs +++ b/src/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs @@ -1,4 +1,4 @@ -using Prism.Navigation; +using Prism.Common; namespace Prism.Ioc; @@ -36,8 +36,14 @@ public static IServiceCollection RegisterForNavigation(this IServiceCollection s if (string.IsNullOrEmpty(name)) name = view.Name; - NavigationRegistry.Register(view, viewModel, name); - services.AddTransient(view); + services.AddSingleton(new ViewRegistration + { + Type = ViewType.Page, + Name = name, + View = view, + ViewModel = viewModel + }) + .AddTransient(view); if (viewModel != null) services.AddTransient(viewModel); diff --git a/src/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs b/src/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs index d4f691f..00a786c 100644 --- a/src/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs +++ b/src/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs @@ -1,4 +1,4 @@ -using Prism.Navigation; +using Prism.Common; namespace Prism.Ioc; @@ -20,8 +20,14 @@ public static IContainerRegistry RegisterForNavigation(this IContainerRegistry c if (string.IsNullOrEmpty(name)) name = view.Name; - NavigationRegistry.Register(view, viewModel, name); - container.Register(view); + container.RegisterInstance(new ViewRegistration + { + Type = ViewType.Page, + Name = name, + View = view, + ViewModel = viewModel + }) + .Register(view); if (viewModel != null) container.Register(viewModel); diff --git a/src/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs b/src/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs index b7543d3..d2ef037 100644 --- a/src/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs +++ b/src/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs @@ -1,4 +1,5 @@ using Microsoft.Maui.Controls.Compatibility; +using Prism.Common; using Prism.Regions; using Prism.Regions.Adapters; using Prism.Regions.Behaviors; @@ -38,9 +39,14 @@ private static IContainerRegistry RegisterForNavigationWithViewModel(this IConta if (viewModelType is not null) containerRegistry.Register(viewModelType); - containerRegistry.Register(viewType); - - RegionNavigationRegistry.Register(viewType, viewModelType, name); + containerRegistry.Register(viewType) + .RegisterInstance(new ViewRegistration + { + Name = name, + Type = ViewType.Region, + View = viewType, + ViewModel = viewModelType + }); return containerRegistry; } @@ -75,15 +81,21 @@ private static IServiceCollection RegisterForNavigationWithViewModel(this IServi if (viewModelType is not null) services.AddTransient(viewModelType); - services.AddTransient(viewType); - - RegionNavigationRegistry.Register(viewType, viewModelType, name); + services.AddTransient(viewType) + .AddSingleton(new ViewRegistration + { + Name = name, + Type = ViewType.Region, + View = viewType, + ViewModel = viewModelType + }); return services; } internal static IContainerRegistry RegisterRegionServices(this IContainerRegistry containerRegistry, Action configureAdapters = null, Action configureBehaviors = null) { + containerRegistry.Register(); containerRegistry.RegisterSingleton(p => { var regionAdapterMappings = new RegionAdapterMappings(); diff --git a/src/Prism.Maui/Mvvm/IViewRegistry.cs b/src/Prism.Maui/Mvvm/IViewRegistry.cs new file mode 100644 index 0000000..f81ac5f --- /dev/null +++ b/src/Prism.Maui/Mvvm/IViewRegistry.cs @@ -0,0 +1,19 @@ +using Prism.Common; +using Prism.Ioc; + +namespace Prism.Mvvm; + +public interface IViewRegistry +{ + IEnumerable Registrations { get; } + + object CreateView(IContainerProvider container, string name); + + Type GetViewType(string name); + + string GetViewModelNavigationKey(Type viewModelType); + + IEnumerable ViewsOfType(Type baseType); + + bool IsRegistered(string name); +} diff --git a/src/Prism.Maui/Mvvm/ViewModelLocator.cs b/src/Prism.Maui/Mvvm/ViewModelLocator.cs index cfd2d83..f4da63c 100644 --- a/src/Prism.Maui/Mvvm/ViewModelLocator.cs +++ b/src/Prism.Maui/Mvvm/ViewModelLocator.cs @@ -18,6 +18,9 @@ public static class ViewModelLocator null, propertyChanged: OnViewModelPropertyChanged); + internal static readonly BindableProperty NavigationNameProperty = + BindableProperty.CreateAttached("NavigationName", typeof(string), typeof(ViewModelLocator), null); + /// /// Gets the AutowireViewModel property value. /// diff --git a/src/Prism.Maui/Mvvm/ViewRegistryBase.cs b/src/Prism.Maui/Mvvm/ViewRegistryBase.cs new file mode 100644 index 0000000..2c2d732 --- /dev/null +++ b/src/Prism.Maui/Mvvm/ViewRegistryBase.cs @@ -0,0 +1,149 @@ +using System.Text.RegularExpressions; +using Prism.Common; +using Prism.Ioc; +using Prism.Navigation.Xaml; + +namespace Prism.Mvvm; + +public abstract class ViewRegistryBase : IViewRegistry +{ + private readonly IEnumerable _registrations; + private readonly ViewType _registryType; + + protected ViewRegistryBase(ViewType registryType, IEnumerable registrations) + { + _registrations = registrations; + _registryType = registryType; + } + + public IEnumerable Registrations => + _registrations.Where(x => x.Type == _registryType); + + public Type GetViewType(string name) => + GetRegistration(name)?.View; + + public object CreateView(IContainerProvider container, string name) + { + try + { + var registration = GetRegistration(name); + if (registration is null) + throw new KeyNotFoundException($"No view with the name '{name}' has been registered"); + + var view = container.Resolve(registration.View) as BindableObject; + view.SetValue(ViewModelLocator.NavigationNameProperty, registration.Name); + + view.SetContainerProvider(container); + ConfigureView(view, container); + + if (registration.ViewModel is not null) + view.SetValue(ViewModelLocator.ViewModelProperty, registration.ViewModel); + + Autowire(view); + + return view; + } + catch (KeyNotFoundException) + { + throw; + } + catch (Exception ex) + { + throw new Exception($"Unable to create {_registryType} '{name}'.", ex); + } + } + + private IEnumerable GetCandidates(Type viewModelType) + { + var names = new List + { + Regex.Replace(viewModelType.Name, @"ViewModel$", string.Empty), + Regex.Replace(viewModelType.Name, @"Model$", string.Empty), + }; + + if (_registryType == ViewType.Page) + names.Add(Regex.Replace(viewModelType.Name, @"ViewModel$", "Page")); + else if (_registryType == ViewType.Region) + names.Add(Regex.Replace(viewModelType.Name, @"ViewModel$", "Region")); + else if (_registryType == ViewType.Dialog) + names.Add(Regex.Replace(viewModelType.Name, @"ViewModel$", "Dialog")); + + names = names.Where(x => !x.EndsWith("PagePage")).ToList(); + + var namespaces = _registryType switch + { + ViewType.Page => new[] + { + viewModelType.Namespace.Replace("ViewModels", "Views"), + viewModelType.Namespace.Replace("ViewModels", "Pages") + }, + ViewType.Region => new[] + { + viewModelType.Namespace.Replace("ViewModels", "Views"), + viewModelType.Namespace.Replace("ViewModels", "Regions") + }, + ViewType.Dialog => new[] + { + viewModelType.Namespace.Replace("ViewModels", "Views"), + viewModelType.Namespace.Replace("ViewModels", "Dialogs") + }, + _ => new[] + { + viewModelType.Namespace.Replace("ViewModels", "Views"), + } + }; + + var candidates = namespaces.Select(@namespace => names.Select(name => $"{@namespace}.{name}")) + .SelectMany(x => x) + .Select(x => viewModelType.AssemblyQualifiedName.Replace(viewModelType.FullName, x)); + return candidates + .Select(x => Type.GetType(x, false)) + .Where(x => x is not null); + } + + public string GetViewModelNavigationKey(Type viewModelType) + { + var registration = Registrations.LastOrDefault(x => x.ViewModel == viewModelType); + if (registration is not null) + return registration.Name; + + var candidates = GetCandidates(viewModelType); + registration = Registrations.LastOrDefault(x => candidates.Any(c => c == x.View)); + if (registration is not null) + { + return registration.Name; + } + + throw new KeyNotFoundException($"No View with the ViewModel '{viewModelType.Name}' has been registered"); + } + + public IEnumerable ViewsOfType(Type baseType) => + Registrations.Where(x => x.View == baseType || x.View.IsAssignableTo(baseType)); + + public bool IsRegistered(string name) => + GetRegistration(name) is not null; + + protected ViewRegistration GetRegistration(string name) => + Registrations.LastOrDefault(x => x.Name == name); + + protected abstract void ConfigureView(BindableObject bindable, IContainerProvider container); + + protected void Autowire(BindableObject view) + { + if (view.BindingContext is not null) + return; + + ViewModelLocator.Autowire(view); + } + + //public static Type GetPageType(string name) + //{ + // var registrations = _registrations.Where(x => x.Name == name); + // if (!registrations.Any()) + // throw new KeyNotFoundException(name); + // if (registrations.Count() > 1) + // throw new InvalidOperationException(string.Format(Resources.MultipleViewsRegisteredForNavigationKey, name, string.Join(", ", registrations.Select(x => x.View.FullName)))); + + // return registrations.First().View; + //} +} diff --git a/src/Prism.Maui/Navigation/Builder/NavigationBuilder.cs b/src/Prism.Maui/Navigation/Builder/NavigationBuilder.cs index 46ea390..4517c5e 100644 --- a/src/Prism.Maui/Navigation/Builder/NavigationBuilder.cs +++ b/src/Prism.Maui/Navigation/Builder/NavigationBuilder.cs @@ -1,6 +1,9 @@ -namespace Prism.Navigation.Builder; +using Prism.Common; +using Prism.Mvvm; -internal class NavigationBuilder : INavigationBuilder +namespace Prism.Navigation.Builder; + +internal class NavigationBuilder : INavigationBuilder, IRegistryAware { internal static readonly Uri RootUri = new Uri("app://prismapp.maui", UriKind.Absolute); @@ -16,6 +19,10 @@ public NavigationBuilder(INavigationService navigationService) _uriSegments = new List(); } + IViewRegistry IRegistryAware.Registry => ((IRegistryAware)_navigationService).Registry; + + public IViewRegistry Registry { get; } + public INavigationBuilder AddNavigationSegment(string segmentName, Action configureSegment) { var builder = new SegmentBuilder(segmentName); @@ -26,7 +33,7 @@ public INavigationBuilder AddNavigationSegment(string segmentName, Action configureSegment) { - var builder = new TabbedSegmentBuilder(); + var builder = new TabbedSegmentBuilder(this); configureSegment?.Invoke(builder); _uriSegments.Add(builder); return this; diff --git a/src/Prism.Maui/Navigation/Builder/NavigationBuilderExtensions.cs b/src/Prism.Maui/Navigation/Builder/NavigationBuilderExtensions.cs index fecf96f..e836728 100644 --- a/src/Prism.Maui/Navigation/Builder/NavigationBuilderExtensions.cs +++ b/src/Prism.Maui/Navigation/Builder/NavigationBuilderExtensions.cs @@ -1,4 +1,5 @@ -using Prism.Navigation.Builder; +using Prism.Common; +using Prism.Navigation.Builder; namespace Prism.Navigation; @@ -7,13 +8,16 @@ public static class NavigationBuilderExtensions public static INavigationBuilder CreateBuilder(this INavigationService navigationService) => new NavigationBuilder(navigationService); - internal static string GetNavigationKey() + internal static string GetNavigationKey(object builder) { var vmType = typeof(TViewModel); if (vmType.IsAssignableFrom(typeof(VisualElement))) throw new NavigationException(NavigationException.MvvmPatternBreak, typeof(TViewModel).Name); - return NavigationRegistry.GetViewModelNavigationKey(vmType); + if (builder is not IRegistryAware registryAware) + throw new Exception("The builder does not implement IRegistryAware"); + + return registryAware.Registry.GetViewModelNavigationKey(vmType); } public static INavigationBuilder UseAbsoluteNavigation(this INavigationBuilder builder) => @@ -30,13 +34,13 @@ public static ICreateTabBuilder AddNavigationSegment(this ICreateTab builder.AddNavigationSegment(b => { }); public static ICreateTabBuilder AddNavigationSegment(this ICreateTabBuilder builder, Action configureSegment) => - builder.AddNavigationSegment(GetNavigationKey(), configureSegment); + builder.AddNavigationSegment(GetNavigationKey(builder), configureSegment); public static INavigationBuilder AddNavigationSegment(this INavigationBuilder builder) => builder.AddNavigationSegment(b => { }); public static INavigationBuilder AddNavigationSegment(this INavigationBuilder builder, Action configureSegment) => - builder.AddNavigationSegment(GetNavigationKey(), configureSegment); + builder.AddNavigationSegment(GetNavigationKey(builder), configureSegment); public static INavigationBuilder AddNavigationSegment(this INavigationBuilder builder, bool useModalNavigation) => builder.AddNavigationSegment(b => b.UseModalNavigation(useModalNavigation)); @@ -47,12 +51,15 @@ public static INavigationBuilder AddNavigationPage(this INavigationBuilder build public static INavigationBuilder AddNavigationPage(this INavigationBuilder builder, Action configureSegment) { - var registrationInfo = NavigationRegistry.Registrations - .FirstOrDefault(x => x.View.IsAssignableTo(typeof(NavigationPage))); - if (registrationInfo is null) + if (builder is not IRegistryAware registryAware) + throw new Exception("The builder does not implement IRegistryAware"); + + var registrations = registryAware.Registry.ViewsOfType(typeof(NavigationPage)); + if (!registrations.Any()) throw new NavigationException(NavigationException.NoPageIsRegistered, nameof(NavigationPage)); - return builder.AddNavigationSegment(registrationInfo.Name, configureSegment); + var registration = registrations.Last(); + return builder.AddNavigationSegment(registration.Name, configureSegment); } public static ICreateTabBuilder AddNavigationPage(this ICreateTabBuilder builder) => @@ -60,12 +67,15 @@ public static ICreateTabBuilder AddNavigationPage(this ICreateTabBuilder builder public static ICreateTabBuilder AddNavigationPage(this ICreateTabBuilder builder, Action configureSegment) { - var registrationInfo = NavigationRegistry.Registrations - .FirstOrDefault(x => x.View.IsAssignableTo(typeof(NavigationPage))); - if (registrationInfo is null) + if (builder is not IRegistryAware registryAware) + throw new Exception("The builder does not implement IRegistryAware"); + + var registrations = registryAware.Registry.ViewsOfType(typeof(NavigationPage)); + if (!registrations.Any()) throw new NavigationException(NavigationException.NoPageIsRegistered, nameof(NavigationPage)); - return builder.AddNavigationSegment(registrationInfo.Name, configureSegment); + var registration = registrations.Last(); + return builder.AddNavigationSegment(registration.Name, configureSegment); } public static INavigationBuilder AddNavigationPage(this INavigationBuilder builder, bool useModalNavigation) => @@ -120,13 +130,13 @@ public static ITabbedSegmentBuilder CreateTab(this ITabbedSegmentBuilder builder public static ITabbedSegmentBuilder CreateTab(this ITabbedSegmentBuilder builder) { - var navigationKey = GetNavigationKey(); + var navigationKey = GetNavigationKey(builder); return builder.CreateTab(navigationKey); } public static ITabbedSegmentBuilder SelectTab(this ITabbedSegmentBuilder builder) { - var navigationKey = GetNavigationKey(); + var navigationKey = GetNavigationKey(builder); return builder.SelectedTab(navigationKey); } diff --git a/src/Prism.Maui/Navigation/Builder/TabbedSegmentBuilder.cs b/src/Prism.Maui/Navigation/Builder/TabbedSegmentBuilder.cs index 744f542..c684690 100644 --- a/src/Prism.Maui/Navigation/Builder/TabbedSegmentBuilder.cs +++ b/src/Prism.Maui/Navigation/Builder/TabbedSegmentBuilder.cs @@ -1,21 +1,31 @@ -namespace Prism.Navigation.Builder; +using Prism.Common; +using Prism.Mvvm; -internal class TabbedSegmentBuilder : ITabbedSegmentBuilder, IConfigurableSegmentName, IUriSegment +namespace Prism.Navigation.Builder; + +internal class TabbedSegmentBuilder : ITabbedSegmentBuilder, IConfigurableSegmentName, IUriSegment, IRegistryAware { private INavigationParameters _parameters { get; } + private INavigationBuilder _builder { get; } - public TabbedSegmentBuilder() + public TabbedSegmentBuilder(INavigationBuilder builder) { + _builder = builder; _parameters = new NavigationParameters(); - var registrationInfo = NavigationRegistry.Registrations - .FirstOrDefault(x => x.View.IsAssignableFrom(typeof(TabbedPage))); - if (registrationInfo is null) - throw new NavigationException(NavigationException.NoPageIsRegistered); + if (builder is not IRegistryAware registryAware) + throw new Exception("The builder does not implement IRegistryAware"); + + var registrations = registryAware.Registry.ViewsOfType(typeof(TabbedPage)); + if (!registrations.Any()) + throw new NavigationException(NavigationException.NoPageIsRegistered, nameof(TabbedPage)); - SegmentName = registrationInfo.Name; + var registration = registrations.Last(); + SegmentName = registration.Name; } + IViewRegistry IRegistryAware.Registry => ((IRegistryAware)_builder).Registry; + public string SegmentName { get; set; } public string Segment => BuildSegment(); diff --git a/src/Prism.Maui/Navigation/INavigationRegistry.cs b/src/Prism.Maui/Navigation/INavigationRegistry.cs new file mode 100644 index 0000000..df104e9 --- /dev/null +++ b/src/Prism.Maui/Navigation/INavigationRegistry.cs @@ -0,0 +1,5 @@ +using Prism.Mvvm; + +namespace Prism.Navigation; + +public interface INavigationRegistry : IViewRegistry { } diff --git a/src/Prism.Maui/Navigation/NavigationRegistry.cs b/src/Prism.Maui/Navigation/NavigationRegistry.cs index 5896b7a..e8d1e38 100644 --- a/src/Prism.Maui/Navigation/NavigationRegistry.cs +++ b/src/Prism.Maui/Navigation/NavigationRegistry.cs @@ -1,140 +1,53 @@ -using System.ComponentModel; -using System.Data; -using System.Text.RegularExpressions; -using Prism.Behaviors; +using Prism.Behaviors; using Prism.Common; using Prism.Ioc; using Prism.Mvvm; -using Prism.Properties; +using Prism.Navigation.Xaml; namespace Prism.Navigation; -public static class NavigationRegistry +internal class NavigationRegistry : ViewRegistryBase, INavigationRegistry { - private static readonly List _registrations = new (); - - internal static IEnumerable Registrations => _registrations; - - public static void Register(string name) => - Register(typeof(TView), typeof(TViewModel), name); - - public static void Register(Type viewType, Type viewModelType, string name) + public NavigationRegistry(IEnumerable registrations) + : base(ViewType.Page, registrations) { - if (_registrations.Any(x => x.Name == name)) - throw new DuplicateNameException($"A view with the name '{name}' has already been registered"); - - var registration = new ViewRegistration - { - View = viewType, - ViewModel = viewModelType, - Name = name - }; - _registrations.Add(registration); } - public static object CreateView(IContainerProvider container, string name) + protected override void ConfigureView(BindableObject bindable, IContainerProvider container) { - try - { - var registration = _registrations.FirstOrDefault(x => x.Name == name); - if (registration is null) - throw new KeyNotFoundException($"No view with the name '{name}' has been registered"); - - var view = container.Resolve(registration.View) as BindableObject; - - view.SetValue(Xaml.Navigation.NavigationScopeProperty, container); - - if (view is Page page) - { - ConfigurePage(container, page); - } - - if (view.BindingContext is not null) - return view; - - if (registration.ViewModel is not null) - view.SetValue(ViewModelLocator.ViewModelProperty, registration.ViewModel); - - ViewModelLocator.Autowire(view); - - return view; - } - catch (KeyNotFoundException) - { - throw; - } - catch (Exception ex) - { - throw new Exception($"Unable to create page '{name}'.", ex); - } + ConfigurePage(container, bindable as Page); } - public static bool IsRegistered(string name) => - _registrations.Any(x => x.Name == name); - - public static Type GetPageType(string name) - { - var registrations = _registrations.Where(x => x.Name == name); - if (!registrations.Any()) - throw new KeyNotFoundException(name); - if (registrations.Count() > 1) - throw new InvalidOperationException(string.Format(Resources.MultipleViewsRegisteredForNavigationKey, name, string.Join(", ", registrations.Select(x => x.View.FullName)))); - - return registrations.First().View; - } - - // To be used for the NavigationBuilder ViewModel Navigation Support - [EditorBrowsable(EditorBrowsableState.Never)] - public static string GetViewModelNavigationKey(Type viewModelType) - { - var registrations = _registrations.Where(x => x.ViewModel == viewModelType); - - if(!registrations.Any()) - { - var names = new[] - { - Regex.Replace(viewModelType.Name, @"ViewModel$", string.Empty), - Regex.Replace(viewModelType.Name, @"Model$", string.Empty), - }; - registrations = _registrations.Where(x => names.Any(n => n == x.View.Name || x.Name == n)); - } - - if (registrations.Count() > 1) - throw new InvalidOperationException($"Multiple Registrations were found for '{viewModelType.FullName}'"); - else if (registrations.Count() == 1) - return registrations.First().Name; - - throw new InvalidOperationException($"No Registrations were found for '{viewModelType.FullName}'"); - } - - public static ViewRegistration GetPageNavigationInfo(Type viewType) => - _registrations.FirstOrDefault(x => x.View == viewType); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static void ClearRegistrationCache() => _registrations.Clear(); - private static void ConfigurePage(IContainerProvider container, Page page) { - if(page is TabbedPage tabbed) + if (page is TabbedPage tabbed) { - foreach(var child in tabbed.Children) + foreach (var child in tabbed.Children) { var scope = container.CreateScope(); ConfigurePage(scope, child); } } - else if(page is NavigationPage navPage && navPage.RootPage is not null) + else if (page is NavigationPage navPage && navPage.RootPage is not null) { var scope = container.CreateScope(); ConfigurePage(scope, navPage.RootPage); } - if (page.GetValue(Xaml.Navigation.NavigationScopeProperty) is null) - page.SetValue(Xaml.Navigation.NavigationScopeProperty, container); - - container.Resolve().Page = page; + if (page.GetContainerProvider() is null) + page.SetContainerProvider(container); - page.SetValue(Xaml.Navigation.NavigationServiceProperty, container.Resolve()); + var accessor = container.Resolve(); + if (accessor.Page is not null && accessor.Page != page) + { +#if DEBUG + if (System.Diagnostics.Debugger.IsAttached) + System.Diagnostics.Debugger.Break(); +#endif + throw new NavigationException($"Invalid Scope provided. The current scope Page Accessor contains '{accessor.Page.GetType().FullName}', expected '{page.GetType().FullName}'.", page); + } + else if (accessor.Page is null) + accessor.Page = page; var behaviorFactories = container.Resolve>(); foreach (var factory in behaviorFactories) diff --git a/src/Prism.Maui/Navigation/PageNavigationService.cs b/src/Prism.Maui/Navigation/PageNavigationService.cs index 578b833..40340b0 100644 --- a/src/Prism.Maui/Navigation/PageNavigationService.cs +++ b/src/Prism.Maui/Navigation/PageNavigationService.cs @@ -1,6 +1,7 @@ using Prism.Common; using Prism.Events; using Prism.Ioc; +using Prism.Mvvm; using Application = Microsoft.Maui.Controls.Application; namespace Prism.Navigation; @@ -8,7 +9,7 @@ namespace Prism.Navigation; /// /// Provides page based navigation for ViewModels. /// -public class PageNavigationService : INavigationService +public class PageNavigationService : INavigationService, IRegistryAware { private static readonly SemaphoreSlim _semaphore = new (1, 1); internal const string RemovePageRelativePath = "../"; @@ -37,18 +38,26 @@ protected Window Window } } + private readonly IViewRegistry _registry; + IViewRegistry IRegistryAware.Registry => _registry; + /// /// Constructs a new instance of the . /// /// The that will be used to resolve pages for navigation. /// The that will let us ensure the Application.MainPage is set. /// The that will raise . - public PageNavigationService(IContainerProvider container, IApplication application, IEventAggregator eventAggregator, IPageAccessor pageAccessor) + public PageNavigationService(IContainerProvider container, + IApplication application, + IEventAggregator eventAggregator, + IPageAccessor pageAccessor, + INavigationRegistry navigationRegistry) { _container = container; _application = application; _eventAggregator = eventAggregator; _pageAccessor = pageAccessor; + _registry = navigationRegistry; } /// @@ -395,7 +404,7 @@ await DoNavigateAction(GetCurrentPage(), nextSegment, nextPage, parameters, asyn protected virtual async Task ProcessNavigationForContentPage(Page currentPage, string nextSegment, Queue segments, INavigationParameters parameters, bool? useModalNavigation, bool animated) { - var nextPageType = NavigationRegistry.GetPageType(UriParsingHelper.GetSegmentName(nextSegment)); + var nextPageType = _registry.GetViewType(UriParsingHelper.GetSegmentName(nextSegment)); bool useReverse = UseReverseNavigation(currentPage, nextPageType) && !(useModalNavigation.HasValue && useModalNavigation.Value); if (!useReverse) { @@ -439,7 +448,7 @@ protected virtual async Task ProcessNavigationForNavigationPage(NavigationPage c } var topPage = currentPage.Navigation.NavigationStack.LastOrDefault(); - var nextPageType = NavigationRegistry.GetPageType(UriParsingHelper.GetSegmentName(nextSegment)); + var nextPageType = _registry.GetViewType(UriParsingHelper.GetSegmentName(nextSegment)); if (topPage?.GetType() == nextPageType) { if (clearNavigationStack) @@ -512,7 +521,7 @@ await DoNavigateAction(currentPage, nextSegment, nextPage, parameters, async () return; } - var nextSegmentType = NavigationRegistry.GetPageType(UriParsingHelper.GetSegmentName(nextSegment)); + var nextSegmentType = _registry.GetViewType(UriParsingHelper.GetSegmentName(nextSegment)); //we must recreate the NavigationPage everytime or the transitions on iOS will not work properly, unless we meet the two scenarios below bool detailIsNavPage = false; @@ -531,7 +540,7 @@ await DoNavigateAction(currentPage, nextSegment, nextPage, parameters, async () { //if we weren't forced to reuse the NavPage, then let's check the NavPage.CurrentPage against the next segment type as we don't want to recreate the entire nav stack //just in case the user is trying to navigate to the same page which may be nested in a NavPage - var nextPageType = NavigationRegistry.GetPageType(UriParsingHelper.GetSegmentName(segments.Peek())); + var nextPageType = _registry.GetViewType(UriParsingHelper.GetSegmentName(segments.Peek())); var currentPageType = navPage.CurrentPage.GetType(); if (nextPageType == currentPageType) { @@ -679,7 +688,7 @@ protected virtual Page CreatePage(string segmentName) try { var scope = _container.CreateScope(); - var page = (Page)NavigationRegistry.CreateView(scope, segmentName); + var page = (Page)_registry.CreateView(scope, segmentName); if (page is null) throw new NullReferenceException($"The resolved type for {segmentName} was null. You may be attempting to navigate to a Non-Page type"); @@ -692,9 +701,9 @@ protected virtual Page CreatePage(string segmentName) throw; else if(ex is KeyNotFoundException) - throw new NavigationException(NavigationException.NoPageIsRegistered, _pageAccessor.Page, ex); + throw new NavigationException(NavigationException.NoPageIsRegistered, segmentName, ex); - throw new NavigationException(NavigationException.ErrorCreatingPage, _pageAccessor.Page, ex); + throw new NavigationException(NavigationException.ErrorCreatingPage, segmentName, ex); } } @@ -763,7 +772,7 @@ void ConfigureTabbedPage(TabbedPage tabbedPage, string segment) TabbedPageSelectTab(tabbedPage, parameters); } - private static void SelectPageTab(Page page, INavigationParameters parameters) + private void SelectPageTab(Page page, INavigationParameters parameters) { if (page is TabbedPage tabbedPage) { @@ -771,12 +780,12 @@ private static void SelectPageTab(Page page, INavigationParameters parameters) } } - private static void TabbedPageSelectTab(TabbedPage tabbedPage, INavigationParameters parameters) + private void TabbedPageSelectTab(TabbedPage tabbedPage, INavigationParameters parameters) { var selectedTab = parameters?.GetValue(KnownNavigationParameters.SelectedTab); if (!string.IsNullOrWhiteSpace(selectedTab)) { - var selectedTabType = NavigationRegistry.GetPageType(UriParsingHelper.GetSegmentName(selectedTab)); + var selectedTabType = _registry.GetViewType(UriParsingHelper.GetSegmentName(selectedTab)); var childFound = false; foreach (var child in tabbedPage.Children) @@ -834,7 +843,7 @@ protected virtual async Task UseReverseNavigation(Page currentPage, string nextS } else { - var pageType = NavigationRegistry.GetPageType(UriParsingHelper.GetSegmentName(item)); + var pageType = _registry.GetViewType(UriParsingHelper.GetSegmentName(item)); if (MvvmHelpers.IsSameOrSubclassOf(pageType)) { illegalSegments.Enqueue(item); diff --git a/src/Prism.Maui/Navigation/Xaml/Navigation.cs b/src/Prism.Maui/Navigation/Xaml/Navigation.cs index 1d7a391..af4fdfb 100644 --- a/src/Prism.Maui/Navigation/Xaml/Navigation.cs +++ b/src/Prism.Maui/Navigation/Xaml/Navigation.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using Prism.Common; using Prism.Ioc; namespace Prism.Navigation.Xaml; @@ -8,22 +9,25 @@ namespace Prism.Navigation.Xaml; /// public static class Navigation { - internal static readonly BindableProperty NavigationServiceProperty = - BindableProperty.CreateAttached("NavigationService", - typeof(INavigationService), - typeof(Navigation), - default(INavigationService)); + internal const string PrismContainerProvider = nameof(PrismContainerProvider); - internal static readonly BindableProperty NavigationScopeProperty = - BindableProperty.CreateAttached("NavigationScope", + private static readonly BindableProperty NavigationScopeProperty = + BindableProperty.CreateAttached(PrismContainerProvider, typeof(IContainerProvider), typeof(Navigation), default(IContainerProvider), propertyChanged: OnNavigationScopeChanged); + [EditorBrowsable(EditorBrowsableState.Never)] + public static readonly BindableProperty ChildViewsProperty = + BindableProperty.CreateAttached("ChildViews", + typeof(IEnumerable), + typeof(Navigation), + null); + private static void OnNavigationScopeChanged(BindableObject bindable, object oldValue, object newValue) { - if (oldValue == newValue) + if (bindable is not Page page || oldValue == newValue) { return; } @@ -36,6 +40,12 @@ private static void OnNavigationScopeChanged(BindableObject bindable, object old if (newValue != null && newValue is IScopedProvider scopedProvider) { + var accessor = scopedProvider.Resolve(); + if (accessor.Page is null) + accessor.Page = page; + else if (accessor.Page != page) + throw new InvalidOperationException($"The Scoped Provider has already been assigned to another page. Expected: '{page.GetType().FullName}' - Found: '{accessor.Page.GetType().FullName}'."); + scopedProvider.IsAttached = true; } } @@ -80,12 +90,41 @@ public static INavigationService GetNavigationService(Page page) { if (page == null) throw new ArgumentNullException(nameof(page)); - var container = page.GetValue(NavigationScopeProperty) as IContainerProvider; - var navigationService = container.Resolve(); + var container = page.GetContainerProvider(); + return container.Resolve(); + } - return navigationService; + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetContainerProvider(this BindableObject bindable, IContainerProvider container) + { + bindable.SetValue(NavigationScopeProperty, container); } + [EditorBrowsable(EditorBrowsableState.Never)] + public static IContainerProvider GetContainerProvider(this BindableObject bindable) + { + if (bindable is null) + return null; + + var container = bindable.GetValue(NavigationScopeProperty) as IContainerProvider; + if (container is not null || bindable is Page) + return container; + else if (bindable is Element element && element.Parent is not null) + return GetContainerProvider(element.Parent); + + return null; + } + + internal static IEnumerable GetChildViews(this Page page) + { + var children = page.GetValue(ChildViewsProperty) as IEnumerable; + if (children is not null) + return children; + + return Array.Empty(); + } + + internal static Action GetRaiseCanExecuteChangedInternal(BindableObject view) => (Action)view.GetValue(RaiseCanExecuteChangedInternalProperty); internal static void SetRaiseCanExecuteChangedInternal(BindableObject view, Action value) => view.SetValue(RaiseCanExecuteChangedInternalProperty, value); diff --git a/src/Prism.Maui/PrismAppBuilder.cs b/src/Prism.Maui/PrismAppBuilder.cs index ce6f8a4..35218c4 100644 --- a/src/Prism.Maui/PrismAppBuilder.cs +++ b/src/Prism.Maui/PrismAppBuilder.cs @@ -7,6 +7,8 @@ using Prism.Modularity; using Prism.Mvvm; using Prism.Navigation; +using Prism.Navigation.Xaml; +using Prism.Regions; using Prism.Regions.Adapters; using Prism.Regions.Behaviors; using Prism.Services; @@ -73,18 +75,9 @@ internal static object DefaultViewModelLocator(object view, Type viewModelType) if (view is not BindableObject bindable) return null; - var container = bindable.GetValue(Navigation.Xaml.Navigation.NavigationScopeProperty) as IContainerProvider; + var container = bindable.GetContainerProvider(); - var overrides = new List<(Type Type, object Instance)>(); - if (container.IsRegistered()) - { - var resolver = container.Resolve(); - var resolverOverrides = resolver.GetOverrides(); - if (resolverOverrides.Any()) - overrides.AddRange(resolverOverrides); - } - - return container.Resolve(viewModelType, overrides.ToArray()); + return container.Resolve(viewModelType); } public PrismAppBuilder RegisterTypes(Action registerTypes) @@ -110,13 +103,21 @@ internal void OnInitialized() } var app = _container.Resolve(); - if (!NavigationRegistry.IsRegistered(nameof(NavigationPage))) + var navRegistry = _container.Resolve(); + if (!navRegistry.IsRegistered(nameof(NavigationPage))) { - NavigationRegistry.Register(typeof(PrismNavigationPage), null, nameof(NavigationPage)); - ((IContainerRegistry)_container).Register(typeof(PrismNavigationPage), () => new PrismNavigationPage()); + var registry = _container as IContainerRegistry; + registry + .Register(() => new PrismNavigationPage()) + .RegisterInstance(new ViewRegistration + { + Name = nameof(NavigationPage), + View = typeof(PrismNavigationPage), + Type = ViewType.Page + }); } - if (!NavigationRegistry.IsRegistered(nameof(TabbedPage))) + if (!navRegistry.IsRegistered(nameof(TabbedPage))) ((IContainerRegistry)_container).RegisterForNavigation(); if (app is ILegacyPrismApplication prismApp) @@ -142,7 +143,7 @@ public PrismAppBuilder ConfigureDefaultViewModelFactory(Func(); containerRegistry.RegisterScoped(); containerRegistry.RegisterScoped(); + containerRegistry.Register(); containerRegistry.RegisterPageBehavior(); containerRegistry.RegisterPageBehavior(); diff --git a/src/Prism.Maui/Regions/Adapters/CarouselViewRegionAdapter.cs b/src/Prism.Maui/Regions/Adapters/CarouselViewRegionAdapter.cs index 275de01..4eea8ec 100644 --- a/src/Prism.Maui/Regions/Adapters/CarouselViewRegionAdapter.cs +++ b/src/Prism.Maui/Regions/Adapters/CarouselViewRegionAdapter.cs @@ -2,6 +2,7 @@ using Prism.Behaviors; using Prism.Common; using Prism.Ioc; +using Prism.Mvvm; using Prism.Properties; using Prism.Regions.Behaviors; using Prism.Regions.Navigation; @@ -112,8 +113,21 @@ private void OnCurrentItemChanged(object sender, CurrentItemChangedEventArgs e) _region.Activate(newActiveView); } - var info = RegionNavigationRegistry.GetViewNavigationInfo(newActiveView.GetType()); - var context = new NavigationContext(_region.NavigationService, new Uri(info.Name, UriKind.RelativeOrAbsolute)); + var name = newActiveView.GetValue(ViewModelLocator.NavigationNameProperty) as string; + if(string.IsNullOrEmpty(name)) + { + var viewType = newActiveView.GetType(); + var registry = _region.Container().Resolve(); + var candidate = registry.ViewsOfType(viewType) + .Where(x => x.Type == ViewType.Region) + .FirstOrDefault(x => x.View == viewType); + if (candidate is null) + name = viewType.FullName; + else + name = candidate.Name; + } + + var context = new NavigationContext(_region.NavigationService, new Uri(name, UriKind.RelativeOrAbsolute)); MvvmHelpers.OnNavigatedFrom(previousView, context); MvvmHelpers.OnNavigatedTo(newActiveView, context); diff --git a/src/Prism.Maui/Regions/Adapters/RegionAdapterBase.cs b/src/Prism.Maui/Regions/Adapters/RegionAdapterBase.cs index 6d95880..120e653 100644 --- a/src/Prism.Maui/Regions/Adapters/RegionAdapterBase.cs +++ b/src/Prism.Maui/Regions/Adapters/RegionAdapterBase.cs @@ -1,5 +1,7 @@ using System.Globalization; +using Prism.Extensions; using Prism.Ioc; +using Prism.Navigation.Xaml; using Prism.Properties; using Prism.Regions.Behaviors; using XamlNavigation = Prism.Navigation.Xaml.Navigation; @@ -34,9 +36,14 @@ protected RegionAdapterBase(IRegionBehaviorFactory regionBehaviorFactory) /// The new instance of that the is bound to. public IRegion Initialize(T regionTarget, string regionName) { - var container = regionTarget.GetValue(XamlNavigation.NavigationScopeProperty) as IContainerProvider; + var page = regionTarget.GetParentPage(); + var container = regionTarget.GetContainerProvider(); IRegion region = CreateRegion(container); region.Name = regionName ?? throw new ArgumentNullException(nameof(regionName)); + if (region is ITargetAwareRegion taRegion) + taRegion.TargetElement = regionTarget; + + page.SetBinding(XamlNavigation.ChildViewsProperty, new Binding(nameof(IRegion.ActiveViews), BindingMode.OneWay, source: region)); SetObservableRegionOnHostingControl(region, regionTarget); diff --git a/src/Prism.Maui/Regions/AllActiveRegion.cs b/src/Prism.Maui/Regions/AllActiveRegion.cs index f6502c9..e93b791 100644 --- a/src/Prism.Maui/Regions/AllActiveRegion.cs +++ b/src/Prism.Maui/Regions/AllActiveRegion.cs @@ -1,5 +1,5 @@ -using Prism.Ioc; -using Prism.Properties; +using Prism.Properties; +using Prism.Regions.Navigation; namespace Prism.Regions; @@ -8,8 +8,8 @@ namespace Prism.Regions; /// public class AllActiveRegion : Region { - public AllActiveRegion(IContainerProvider container) - : base(container) + public AllActiveRegion(IRegionNavigationService regionNavigationService) + : base(regionNavigationService) { } diff --git a/src/Prism.Maui/Regions/Behaviors/AutoPopulateRegionBehavior.cs b/src/Prism.Maui/Regions/Behaviors/AutoPopulateRegionBehavior.cs index def74eb..4959c6c 100644 --- a/src/Prism.Maui/Regions/Behaviors/AutoPopulateRegionBehavior.cs +++ b/src/Prism.Maui/Regions/Behaviors/AutoPopulateRegionBehavior.cs @@ -56,7 +56,7 @@ private void StartPopulatingContent() /// protected virtual IEnumerable CreateViewsToAutoPopulate() { - return regionViewRegistry.GetContents(Region.Name); + return regionViewRegistry.GetContents(Region.Name, Region.Container()); } /// @@ -90,7 +90,7 @@ public virtual void OnViewRegistered(object sender, ViewRegisteredEventArgs e) if (e.RegionName == Region.Name) { - AddViewIntoRegion((VisualElement)e.GetView()); + AddViewIntoRegion((VisualElement)e.GetView(Region.Container())); } } } diff --git a/src/Prism.Maui/Regions/Behaviors/DelayedRegionCreationBehavior.cs b/src/Prism.Maui/Regions/Behaviors/DelayedRegionCreationBehavior.cs index 90e0c17..c714d4a 100644 --- a/src/Prism.Maui/Regions/Behaviors/DelayedRegionCreationBehavior.cs +++ b/src/Prism.Maui/Regions/Behaviors/DelayedRegionCreationBehavior.cs @@ -3,6 +3,7 @@ using Prism.Behaviors; using Prism.Extensions; using Prism.Ioc; +using Prism.Navigation.Xaml; using Prism.Properties; using Prism.Regions.Adapters; using XamlNavigation = Prism.Navigation.Xaml.Navigation; @@ -129,8 +130,8 @@ protected virtual IRegion CreateRegion(VisualElement targetElement, string regio throw new Exception("The Target Element has not yet been parented and we cannot get the parent page."); // Build the region - var container = page.GetValue(XamlNavigation.NavigationScopeProperty) as IContainerProvider; - targetElement.SetValue(XamlNavigation.NavigationScopeProperty, container); + var container = page.GetContainerProvider(); + targetElement.SetContainerProvider(container); var regionAdapter = _regionAdapterMappings.GetMapping(targetElement.GetType()); var region = regionAdapter.Initialize(targetElement, regionName); var cleanupBehavior = new RegionCleanupBehavior(region); diff --git a/src/Prism.Maui/Regions/IRegionNavigationRegistry.cs b/src/Prism.Maui/Regions/IRegionNavigationRegistry.cs new file mode 100644 index 0000000..c347031 --- /dev/null +++ b/src/Prism.Maui/Regions/IRegionNavigationRegistry.cs @@ -0,0 +1,5 @@ +using Prism.Mvvm; + +namespace Prism.Regions; + +public interface IRegionNavigationRegistry : IViewRegistry { } diff --git a/src/Prism.Maui/Regions/IRegionViewRegistry.cs b/src/Prism.Maui/Regions/IRegionViewRegistry.cs index 386b32b..1b28cc9 100644 --- a/src/Prism.Maui/Regions/IRegionViewRegistry.cs +++ b/src/Prism.Maui/Regions/IRegionViewRegistry.cs @@ -1,4 +1,6 @@ -namespace Prism.Regions; +using Prism.Ioc; + +namespace Prism.Regions; /// /// Defines the interface for the registry of region's content. @@ -19,7 +21,7 @@ public interface IRegionViewRegistry /// /// Region name for which contents are requested. /// Collection of contents associated with the . - IEnumerable GetContents(string regionName); + IEnumerable GetContents(string regionName, IContainerProvider container); /// /// Registers a content type with a region name. @@ -28,10 +30,17 @@ public interface IRegionViewRegistry /// Content type to be registered for the . void RegisterViewWithRegion(string regionName, Type viewType); + /// + /// Registers a content type with a region name. + /// + /// Region name to which the will be registered. + /// Content type to be registered for the . + void RegisterViewWithRegion(string regionName, string targetName); + /// /// Registers a delegate that can be used to retrieve the content associated with a region name. /// /// Region name to which the will be registered. /// Delegate used to retrieve the content associated with the . - void RegisterViewWithRegion(string regionName, Func getContentDelegate); + void RegisterViewWithRegion(string regionName, Func getContentDelegate); } diff --git a/src/Prism.Maui/Regions/ITargetAwareRegion.cs b/src/Prism.Maui/Regions/ITargetAwareRegion.cs new file mode 100644 index 0000000..4a809b6 --- /dev/null +++ b/src/Prism.Maui/Regions/ITargetAwareRegion.cs @@ -0,0 +1,12 @@ +using System.ComponentModel; +using Prism.Ioc; +using Prism.Navigation.Xaml; + +namespace Prism.Regions; + +[EditorBrowsable(EditorBrowsableState.Never)] +public interface ITargetAwareRegion : IRegion +{ + VisualElement TargetElement { get; set; } + IContainerProvider Container => TargetElement.GetContainerProvider(); +} diff --git a/src/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs b/src/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs index 8638899..e1beb3b 100644 --- a/src/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs +++ b/src/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs @@ -12,17 +12,6 @@ namespace Prism.Regions.Navigation; /// public class RegionNavigationContentLoader : IRegionNavigationContentLoader { - private readonly IContainerExtension _container; - - /// - /// Initializes a new instance of the class with a service locator. - /// - /// The . - public RegionNavigationContentLoader(IContainerExtension container) - { - _container = container; - } - /// /// Gets the view to which the navigation request represented by applies. /// @@ -59,7 +48,7 @@ public object LoadContent(IRegion region, INavigationContext navigationContext) return view; } - view = CreateNewRegionItem(candidateTargetContract) as VisualElement; + view = CreateNewRegionItem(candidateTargetContract, region) as VisualElement; region.Add(view); return view; @@ -70,11 +59,16 @@ public object LoadContent(IRegion region, INavigationContext navigationContext) /// /// The target contract to build. /// An instance of an item to put into the . - protected virtual object CreateNewRegionItem(string candidateTargetContract) + protected virtual object CreateNewRegionItem(string candidateTargetContract, IRegion region) { try { - return RegionNavigationRegistry.CreateView(_container, candidateTargetContract); + var registry = region.Container().Resolve(); + return registry.CreateView(region.Container(), candidateTargetContract); + } + catch (KeyNotFoundException) + { + throw; } catch (ContainerResolutionException) { @@ -124,18 +118,14 @@ protected virtual IEnumerable GetCandidatesFromRegion(IRegion reg if (!contractCandidates.Any()) { - var matchingType = RegionNavigationRegistry.GetViewType(candidateNavigationContract); - if(matchingType is null) - { - matchingType = _container.GetRegistrationType(candidateNavigationContract); - } - - if (matchingType is null) + var registry = region.Container().Resolve(); + var registration = registry.Registrations.FirstOrDefault(x => x.Type == ViewType.Region && (x.Name == candidateNavigationContract || x.View.Name == candidateNavigationContract || x.View.FullName == candidateNavigationContract)); + if(registration is null) { - return Array.Empty(); + GetCandidatesFromRegionViews(region, registration.View.FullName); } - return GetCandidatesFromRegionViews(region, matchingType.FullName); + return Array.Empty(); } return contractCandidates; diff --git a/src/Prism.Maui/Regions/Region.cs b/src/Prism.Maui/Regions/Region.cs index 6eef9cd..3b59d79 100644 --- a/src/Prism.Maui/Regions/Region.cs +++ b/src/Prism.Maui/Regions/Region.cs @@ -2,6 +2,7 @@ using System.Globalization; using Prism.Ioc; using Prism.Mvvm; +using Prism.Navigation.Xaml; using Prism.Properties; using Prism.Regions.Behaviors; using Prism.Regions.Navigation; @@ -11,27 +12,26 @@ namespace Prism.Regions; /// /// Implementation of that allows multiple active views. /// -public class Region : BindableBase, IRegion +public class Region : BindableBase, IRegion, ITargetAwareRegion { private ObservableCollection _itemMetadataCollection; private IRegionManager _regionManager; private readonly IRegionNavigationService _regionNavigationService; - private readonly IContainerProvider _container; - private Comparison _sort; /// /// Initializes a new instance of . /// - public Region(IContainerProvider container) + public Region(IRegionNavigationService regionNavigationService) { - _container = container; Behaviors = new RegionBehaviorCollection(this); - _regionNavigationService = _container.Resolve(); + _regionNavigationService = regionNavigationService; _regionNavigationService.Region = this; _sort = DefaultSortComparison; } + public VisualElement TargetElement { get; set; } + private ViewsCollection _views; /// /// Gets a readonly view of the collection of views in the region. @@ -211,7 +211,9 @@ public virtual void Deactivate(VisualElement view) public IRegionManager Add(string viewName) { - var view = RegionNavigationRegistry.CreateView(_container, viewName) as VisualElement; + var container = TargetElement.GetContainerProvider(); + var registry = container.Resolve(); + var view = registry.CreateView(container, viewName) as VisualElement; return Add(view, viewName); } diff --git a/src/Prism.Maui/Regions/RegionExtensions.cs b/src/Prism.Maui/Regions/RegionExtensions.cs new file mode 100644 index 0000000..7160585 --- /dev/null +++ b/src/Prism.Maui/Regions/RegionExtensions.cs @@ -0,0 +1,14 @@ +using Prism.Ioc; + +namespace Prism.Regions; + +internal static class RegionExtensions +{ + internal static IContainerProvider Container(this IRegion region) + { + if (region is ITargetAwareRegion car) + return car.Container; + + throw new NotSupportedException("The Region does not implement IContainerAwareRegion"); + } +} diff --git a/src/Prism.Maui/Regions/RegionManager.cs b/src/Prism.Maui/Regions/RegionManager.cs index 94c8e8a..e89a2ce 100644 --- a/src/Prism.Maui/Regions/RegionManager.cs +++ b/src/Prism.Maui/Regions/RegionManager.cs @@ -61,9 +61,7 @@ public IRegionManager CreateRegionManager() => public IRegionManager RegisterViewWithRegion(string regionName, Type viewType) { var regionViewRegistry = ContainerLocator.Container.Resolve(); - regionViewRegistry.RegisterViewWithRegion(regionName, viewType); - return this; } @@ -77,9 +75,9 @@ public IRegionManager RegisterViewWithRegion(string regionName, Type viewType) /// The , for adding several views easily public IRegionManager RegisterViewWithRegion(string regionName, string targetName) { - var viewType = ContainerLocator.Current.GetRegistrationType(targetName); - - return RegisterViewWithRegion(regionName, viewType); + var regionViewRegistry = ContainerLocator.Container.Resolve(); + regionViewRegistry.RegisterViewWithRegion(regionName, targetName); + return this; } /// diff --git a/src/Prism.Maui/Regions/RegionNavigationRegistry.cs b/src/Prism.Maui/Regions/RegionNavigationRegistry.cs index 64fb594..ff593f9 100644 --- a/src/Prism.Maui/Regions/RegionNavigationRegistry.cs +++ b/src/Prism.Maui/Regions/RegionNavigationRegistry.cs @@ -1,109 +1,17 @@ -using System.ComponentModel; -using System.Data; +using Prism.Common; using Prism.Ioc; using Prism.Mvvm; -using Prism.Navigation; -using Prism.Properties; namespace Prism.Regions; -public static class RegionNavigationRegistry +internal class RegionNavigationRegistry : ViewRegistryBase, IRegionNavigationRegistry { - private static readonly List _registrations = new(); - - internal static IEnumerable Registrations => _registrations; - - public static void Register(string name) => - Register(typeof(TView), typeof(TViewModel), name); - - public static void Register(Type viewType, Type viewModelType, string name) + public RegionNavigationRegistry(IEnumerable registrations) + : base(ViewType.Region, registrations) { - if (_registrations.Any(x => x.Name == name)) - throw new DuplicateNameException($"A view with the name '{name}' has already been registered"); - - var registration = new ViewRegistration - { - View = viewType, - ViewModel = viewModelType, - Name = name - }; - _registrations.Add(registration); } - public static object CreateView(IContainerProvider container, string name) + protected override void ConfigureView(BindableObject bindable, IContainerProvider container) { - try - { - var registration = _registrations.FirstOrDefault(x => x.Name == name); - if (registration is null) - throw new KeyNotFoundException($"No view with the name '{name}' has been registered"); - - var view = container.Resolve(registration.View) as BindableObject; - - view.SetValue(Prism.Navigation.Xaml.Navigation.NavigationScopeProperty, container); - view.SetValue(Prism.Navigation.Xaml.Navigation.NavigationServiceProperty, container.Resolve()); - - if (view.BindingContext is not null) - return view; - - if (registration.ViewModel is not null) - view.SetValue(ViewModelLocator.ViewModelProperty, registration.ViewModel); - - ViewModelLocator.Autowire(view); - - return view; - } - catch (KeyNotFoundException) - { - throw; - } - catch (Exception ex) - { - throw new Exception($"Unable to create page '{name}'.", ex); - } } - - public static bool IsRegistered(string name) => - _registrations.Any(x => x.Name == name); - - public static Type GetViewType(string name) - { - var registrations = _registrations.Where(x => x.Name == name); - if (!registrations.Any()) - throw new KeyNotFoundException(name); - if (registrations.Count() > 1) - throw new InvalidOperationException(string.Format(Resources.MultipleViewsRegisteredForNavigationKey, name, string.Join(", ", registrations.Select(x => x.View.FullName)))); - - return registrations.First().View; - } - - //// To be used for the NavigationBuilder ViewModel Navigation Support - //[EditorBrowsable(EditorBrowsableState.Never)] - //public static string GetViewModelNavigationKey(Type viewModelType) - //{ - // var registrations = _registrations.Where(x => x.ViewModel == viewModelType); - - // if (!registrations.Any()) - // { - // var names = new[] - // { - // Regex.Replace(viewModelType.Name, @"ViewModel$", string.Empty), - // Regex.Replace(viewModelType.Name, @"Model$", string.Empty), - // }; - // registrations = _registrations.Where(x => names.Any(n => n == x.View.Name || x.Name == n)); - // } - - // if (registrations.Count() > 1) - // throw new InvalidOperationException($"Multiple Registrations were found for '{viewModelType.FullName}'"); - // else if (registrations.Count() == 1) - // return registrations.First().Name; - - // throw new InvalidOperationException($"No Registrations were found for '{viewModelType.FullName}'"); - //} - - public static ViewRegistration GetViewNavigationInfo(Type viewType) => - _registrations.FirstOrDefault(x => x.View == viewType); - - [EditorBrowsable(EditorBrowsableState.Never)] - public static void ClearRegistrationCache() => _registrations.Clear(); } diff --git a/src/Prism.Maui/Regions/RegionViewRegistry.cs b/src/Prism.Maui/Regions/RegionViewRegistry.cs index d8d99b1..6ea1f18 100644 --- a/src/Prism.Maui/Regions/RegionViewRegistry.cs +++ b/src/Prism.Maui/Regions/RegionViewRegistry.cs @@ -12,18 +12,8 @@ namespace Prism.Regions; /// public class RegionViewRegistry : IRegionViewRegistry { - private readonly IContainerProvider _container; - private readonly ListDictionary> _registeredContent = new ListDictionary>(); - private readonly WeakDelegatesManager _contentRegisteredListeners = new WeakDelegatesManager(); - - /// - /// Creates a new instance of the class. - /// - /// used to create the instance of the views from its . - public RegionViewRegistry(IContainerExtension container) - { - _container = container; - } + private readonly ListDictionary> _registeredContent = new (); + private readonly WeakDelegatesManager _contentRegisteredListeners = new (); /// /// Occurs whenever a new view is registered. @@ -39,12 +29,12 @@ public event EventHandler ContentRegistered /// /// Name of the region which content is being requested. /// Collection of contents registered for the region. - public IEnumerable GetContents(string regionName) + public IEnumerable GetContents(string regionName, IContainerProvider container) { var items = new List(); - foreach (Func getContentDelegate in _registeredContent[regionName]) + foreach (var getContentDelegate in _registeredContent[regionName]) { - items.Add(getContentDelegate()); + items.Add(getContentDelegate(container)); } return items; @@ -57,32 +47,40 @@ public IEnumerable GetContents(string regionName) /// Content type to be registered for the . public void RegisterViewWithRegion(string regionName, Type viewType) { - RegisterViewWithRegion(regionName, () => CreateInstance(viewType)); + RegisterViewWithRegion(regionName, c => + { + var registry = c.Resolve(); + var registration = registry.Registrations.FirstOrDefault(x => x.Type == ViewType.Region && x.View == viewType); + if (registration is null) + throw new KeyNotFoundException($"No registration found for the Region View '{viewType.FullName}'."); + + return registry.CreateView(c, registration.Name); + }); } /// - /// Registers a delegate that can be used to retrieve the content associated with a region name. + /// Registers a content type with a region name. /// - /// Region name to which the will be registered. - /// Delegate used to retrieve the content associated with the . - public void RegisterViewWithRegion(string regionName, Func getContentDelegate) + /// Region name to which the will be registered. + /// Content type to be registered for the . + public void RegisterViewWithRegion(string regionName, string targetName) { - _registeredContent.Add(regionName, getContentDelegate); - OnContentRegistered(new ViewRegisteredEventArgs(regionName, getContentDelegate)); + RegisterViewWithRegion(regionName, c => + { + var registry = c.Resolve(); + return registry.CreateView(c, targetName); + }); } /// - /// Creates an instance of a registered view . + /// Registers a delegate that can be used to retrieve the content associated with a region name. /// - /// Type of the registered view. - /// Instance of the registered view. - protected virtual object CreateInstance(Type type) + /// Region name to which the will be registered. + /// Delegate used to retrieve the content associated with the . + public void RegisterViewWithRegion(string regionName, Func getContentDelegate) { - var registration = RegionNavigationRegistry.GetViewNavigationInfo(type); - if (registration is null) - throw new ViewRegistrationException($"The specified view type '{type.FullName}' has not been registered for Region Navigation"); - - return RegionNavigationRegistry.CreateView(_container, registration.Name); + _registeredContent.Add(regionName, getContentDelegate); + OnContentRegistered(new ViewRegisteredEventArgs(regionName, getContentDelegate)); } private void OnContentRegistered(ViewRegisteredEventArgs e) diff --git a/src/Prism.Maui/Regions/SingleActiveRegion.cs b/src/Prism.Maui/Regions/SingleActiveRegion.cs index 599d0ac..6f19bec 100644 --- a/src/Prism.Maui/Regions/SingleActiveRegion.cs +++ b/src/Prism.Maui/Regions/SingleActiveRegion.cs @@ -1,4 +1,4 @@ -using Prism.Ioc; +using Prism.Regions.Navigation; namespace Prism.Regions; @@ -7,8 +7,8 @@ namespace Prism.Regions; /// public class SingleActiveRegion : Region { - public SingleActiveRegion(IContainerProvider container) - : base(container) + public SingleActiveRegion(IRegionNavigationService regionNavigationService) + : base(regionNavigationService) { } diff --git a/src/Prism.Maui/Regions/ViewRegisteredEventArgs.cs b/src/Prism.Maui/Regions/ViewRegisteredEventArgs.cs index cce39f8..d0b29c8 100644 --- a/src/Prism.Maui/Regions/ViewRegisteredEventArgs.cs +++ b/src/Prism.Maui/Regions/ViewRegisteredEventArgs.cs @@ -1,4 +1,6 @@ -namespace Prism.Regions; +using Prism.Ioc; + +namespace Prism.Regions; /// /// Argument class used by the event when a new content is registered. @@ -10,7 +12,7 @@ public class ViewRegisteredEventArgs : EventArgs /// /// The region name to which the content was registered. /// The content which was registered. - public ViewRegisteredEventArgs(string regionName, Func getViewDelegate) + public ViewRegisteredEventArgs(string regionName, Func getViewDelegate) { GetView = getViewDelegate; RegionName = regionName; @@ -24,5 +26,5 @@ public ViewRegisteredEventArgs(string regionName, Func getViewDelegate) /// /// Gets the content which was registered. /// - public Func GetView { get; private set; } + public Func GetView { get; private set; } } diff --git a/tests/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs b/tests/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs new file mode 100644 index 0000000..6640c37 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Fixtures/Navigation/NavigationTests.cs @@ -0,0 +1,58 @@ +using Prism.Controls; +using Prism.DryIoc.Maui.Tests.Mocks; + +namespace Prism.DryIoc.Maui.Tests.Fixtures.Navigation; + +public class NavigationTests +{ + [Theory] + [InlineData("NavigationPage/MockViewA/MockViewB/MockViewC")] + [InlineData("MockHome/NavigationPage/MockViewA")] + public void PagesInjectScopedInstanceOfIPageAccessor(string uri) + { + var mauiApp = CreateBuilder() + .OnAppStart(navigation => navigation.NavigateAsync(uri)) + .Build(); + var app = mauiApp.Services.GetRequiredService() as Application; + var window = app!.Windows.First(); + + var rootPage = window.Page; + + if(rootPage is FlyoutPage flyoutPage) + { + TestPage(flyoutPage); + rootPage = flyoutPage.Detail; + } + + TestPage(rootPage!); + + foreach (var page in rootPage!.Navigation.NavigationStack) + { + TestPage(page); + } + } + + private void TestPage(Page page) + { + if (page is NavigationPage) + { + Assert.IsType(page); + return; + } + + var viewModel = page.BindingContext as MockViewModelBase; + Assert.NotNull(viewModel); + Assert.Same(page, viewModel!.Page); + } + + private PrismAppBuilder CreateBuilder() => + MauiApp.CreateBuilder() + .UsePrismApp() + .RegisterTypes(container => + { + container.RegisterForNavigation() + .RegisterForNavigation() + .RegisterForNavigation() + .RegisterForNavigation(); + }); +} diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockHome.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockHome.cs new file mode 100644 index 0000000..e9a1f79 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockHome.cs @@ -0,0 +1,9 @@ +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockHome : FlyoutPage +{ + public MockHome() + { + Flyout = new ContentPage { Title = "Menu" }; + } +} diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockHomeViewModel.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockHomeViewModel.cs new file mode 100644 index 0000000..9cd148b --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockHomeViewModel.cs @@ -0,0 +1,10 @@ +using Prism.Common; + +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockHomeViewModel : MockViewModelBase +{ + public MockHomeViewModel(IPageAccessor pageAccessor, INavigationService navigationService) : base(pageAccessor, navigationService) + { + } +} diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewA.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewA.cs new file mode 100644 index 0000000..a8a7c56 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewA.cs @@ -0,0 +1,3 @@ +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockViewA : ContentPage { } diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewAViewModel.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewAViewModel.cs new file mode 100644 index 0000000..ce4414a --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewAViewModel.cs @@ -0,0 +1,10 @@ +using Prism.Common; + +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockViewAViewModel : MockViewModelBase +{ + public MockViewAViewModel(IPageAccessor pageAccessor, INavigationService navigationService) : base(pageAccessor, navigationService) + { + } +} diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewB.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewB.cs new file mode 100644 index 0000000..9b333e9 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewB.cs @@ -0,0 +1,3 @@ +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockViewB : ContentPage { } diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewBViewModel.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewBViewModel.cs new file mode 100644 index 0000000..6c6c063 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewBViewModel.cs @@ -0,0 +1,10 @@ +using Prism.Common; + +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockViewBViewModel : MockViewModelBase +{ + public MockViewBViewModel(IPageAccessor pageAccessor, INavigationService navigationService) : base(pageAccessor, navigationService) + { + } +} diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewC.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewC.cs new file mode 100644 index 0000000..905a323 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewC.cs @@ -0,0 +1,3 @@ +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockViewC : ContentPage { } diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewCViewModel.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewCViewModel.cs new file mode 100644 index 0000000..5fb09e1 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewCViewModel.cs @@ -0,0 +1,10 @@ +using Prism.Common; + +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public class MockViewCViewModel : MockViewModelBase +{ + public MockViewCViewModel(IPageAccessor pageAccessor, INavigationService navigationService) : base(pageAccessor, navigationService) + { + } +} diff --git a/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewModelBase.cs b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewModelBase.cs new file mode 100644 index 0000000..9b37d66 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Mocks/MockViewModelBase.cs @@ -0,0 +1,23 @@ +using Prism.Common; + +namespace Prism.DryIoc.Maui.Tests.Mocks; + +public abstract class MockViewModelBase : IConfirmNavigation +{ + private readonly IPageAccessor _pageAccessor; + + protected MockViewModelBase(IPageAccessor pageAccessor, INavigationService navigationService) + { + _pageAccessor = pageAccessor; + NavigationService = navigationService; + } + + public INavigationService NavigationService { get; } + + public Page? Page => _pageAccessor.Page; + + public bool StopNavigation { get; set; } + + public bool CanNavigate(INavigationParameters parameters) => + !StopNavigation; +} \ No newline at end of file diff --git a/tests/Prism.DryIoc.Maui.Tests/Prism.DryIoc.Maui.Tests.csproj b/tests/Prism.DryIoc.Maui.Tests/Prism.DryIoc.Maui.Tests.csproj new file mode 100644 index 0000000..f5692a0 --- /dev/null +++ b/tests/Prism.DryIoc.Maui.Tests/Prism.DryIoc.Maui.Tests.csproj @@ -0,0 +1,44 @@ + + + + net6.0 + enable + enable + false + true + + + + + + + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + +