diff --git a/src/Maui/Prism.Maui/Ioc/DialogRegistrationExtensions.cs b/src/Maui/Prism.Maui/Ioc/DialogRegistrationExtensions.cs index 34cb234b5a..984609cbf6 100644 --- a/src/Maui/Prism.Maui/Ioc/DialogRegistrationExtensions.cs +++ b/src/Maui/Prism.Maui/Ioc/DialogRegistrationExtensions.cs @@ -1,4 +1,4 @@ -using Prism.Common; +using Prism.Mvvm; using Prism.Services; namespace Prism.Ioc; diff --git a/src/Maui/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs b/src/Maui/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs index aef5cf5ef5..ddacf64132 100644 --- a/src/Maui/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs +++ b/src/Maui/Prism.Maui/Ioc/MicrosoftDependencyInjectionExtensions.cs @@ -1,4 +1,4 @@ -using Prism.Common; +using Prism.Mvvm; namespace Prism.Ioc; diff --git a/src/Maui/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs b/src/Maui/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs index 00a786c1e8..688c886ed6 100644 --- a/src/Maui/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs +++ b/src/Maui/Prism.Maui/Ioc/NavigationRegistrationExtensions.cs @@ -1,4 +1,4 @@ -using Prism.Common; +using Prism.Mvvm; namespace Prism.Ioc; diff --git a/src/Maui/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs b/src/Maui/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs index d2ef037d71..086cb6c664 100644 --- a/src/Maui/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs +++ b/src/Maui/Prism.Maui/Ioc/RegionNavigationRegistrationExtensions.cs @@ -1,5 +1,5 @@ using Microsoft.Maui.Controls.Compatibility; -using Prism.Common; +using Prism.Mvvm; using Prism.Regions; using Prism.Regions.Adapters; using Prism.Regions.Behaviors; diff --git a/src/Maui/Prism.Maui/Mvvm/IViewRegistry.cs b/src/Maui/Prism.Maui/Mvvm/IViewRegistry.cs deleted file mode 100644 index f81ac5f16d..0000000000 --- a/src/Maui/Prism.Maui/Mvvm/IViewRegistry.cs +++ /dev/null @@ -1,19 +0,0 @@ -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/Maui/Prism.Maui/Mvvm/ViewModelCreationException.cs b/src/Maui/Prism.Maui/Mvvm/ViewModelCreationException.cs deleted file mode 100644 index 2e1c8f7b6a..0000000000 --- a/src/Maui/Prism.Maui/Mvvm/ViewModelCreationException.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Prism.Mvvm; - -public class ViewModelCreationException : Exception -{ - public ViewModelCreationException(object view, Exception innerException) - : base($"Unable to Create ViewModel for '{view.GetType().FullName}'.", innerException) - { - if (view is VisualElement visualElement) - { - View = visualElement; - ViewName = (string)visualElement.GetValue(ViewModelLocator.NavigationNameProperty); - } - } - - public string ViewName { get; } - - public VisualElement View { get; } -} diff --git a/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs b/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs index f7791dda96..0f565699eb 100644 --- a/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs +++ b/src/Maui/Prism.Maui/Mvvm/ViewRegistryBase.cs @@ -1,154 +1,35 @@ -using System.Text.RegularExpressions; -using System.Xml.Linq; -using Prism.Common; -using Prism.Ioc; +using Prism.Ioc; using Prism.Navigation.Xaml; namespace Prism.Mvvm; -public abstract class ViewRegistryBase : IViewRegistry +public abstract class ViewRegistryBase : ViewRegistryBase { - private readonly IEnumerable _registrations; - private readonly ViewType _registryType; - protected ViewRegistryBase(ViewType registryType, IEnumerable registrations) + : base(registryType, 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) + protected override void Autowire(BindableObject view) { - 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); + if (view.BindingContext is not null) + return; - return view; - } - catch (KeyNotFoundException) - { - throw; - } - catch (ViewModelCreationException) - { - throw; - } - catch (Exception ex) - { - throw new ViewCreationException(name, _registryType, ex); - } + ViewModelLocator.Autowire(view); } - private IEnumerable GetCandidates(Type viewModelType) + protected override void SetContainerProvider(BindableObject view, IContainerProvider container) { - 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); + view.SetContainerProvider(container); } - public string GetViewModelNavigationKey(Type viewModelType) + protected override void SetNavigationNameProperty(BindableObject view, string name) { - 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"); + view.SetValue(ViewModelLocator.NavigationNameProperty, name); } - 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) + protected override void SetViewModelProperty(BindableObject view, Type viewModelType) { - if (view.BindingContext is not null) - return; - - ViewModelLocator.Autowire(view); + view.SetValue(ViewModelLocator.ViewModelProperty, viewModelType); } - - //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/Maui/Prism.Maui/PrismAppBuilder.cs b/src/Maui/Prism.Maui/PrismAppBuilder.cs index 03c1b92aff..7792cff8af 100644 --- a/src/Maui/Prism.Maui/PrismAppBuilder.cs +++ b/src/Maui/Prism.Maui/PrismAppBuilder.cs @@ -34,6 +34,14 @@ internal PrismAppBuilder(IContainerExtension containerExtension, MauiAppBuilder _registrations = new List>(); _initializations = new List>(); + ViewModelCreationException.SetViewNameDelegate(view => + { + if (view is BindableObject bindable) + return Mvvm.ViewModelLocator.GetNavigationName(bindable); + + return $"View is not a BindableObject: '{view.GetType().FullName}"; + }); + MauiBuilder = builder; MauiBuilder.ConfigureContainer(new PrismServiceProviderFactory(RegistrationCallback)); MauiBuilder.ConfigureLifecycleEvents(lifecycle => diff --git a/src/Maui/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs b/src/Maui/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs index e1beb3bd77..12f6cf2add 100644 --- a/src/Maui/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs +++ b/src/Maui/Prism.Maui/Regions/Navigation/RegionNavigationContentLoader.cs @@ -2,6 +2,7 @@ using Prism.Common; using Prism.Ioc; using Prism.Ioc.Internals; +using Prism.Mvvm; using Prism.Properties; namespace Prism.Regions.Navigation; diff --git a/src/Maui/Prism.Maui/Regions/RegionNavigationRegistry.cs b/src/Maui/Prism.Maui/Regions/RegionNavigationRegistry.cs index ff593f95ad..8765ef9685 100644 --- a/src/Maui/Prism.Maui/Regions/RegionNavigationRegistry.cs +++ b/src/Maui/Prism.Maui/Regions/RegionNavigationRegistry.cs @@ -1,5 +1,4 @@ -using Prism.Common; -using Prism.Ioc; +using Prism.Ioc; using Prism.Mvvm; namespace Prism.Regions; diff --git a/src/Maui/Prism.Maui/Regions/RegionViewRegistry.cs b/src/Maui/Prism.Maui/Regions/RegionViewRegistry.cs index 6ea1f18689..094d43391a 100644 --- a/src/Maui/Prism.Maui/Regions/RegionViewRegistry.cs +++ b/src/Maui/Prism.Maui/Regions/RegionViewRegistry.cs @@ -3,6 +3,7 @@ using Prism.Common; using Prism.Events; using Prism.Ioc; +using Prism.Mvvm; using Prism.Properties; namespace Prism.Regions; diff --git a/src/Maui/Prism.Maui/Services/Dialogs/DialogViewRegistry.cs b/src/Maui/Prism.Maui/Services/Dialogs/DialogViewRegistry.cs index ca449576ff..b1d799764c 100644 --- a/src/Maui/Prism.Maui/Services/Dialogs/DialogViewRegistry.cs +++ b/src/Maui/Prism.Maui/Services/Dialogs/DialogViewRegistry.cs @@ -1,6 +1,5 @@ using Prism.Ioc; using Prism.Mvvm; -using Prism.Common; namespace Prism.Services; diff --git a/src/Maui/Prism.Maui/Common/IRegistryAware.cs b/src/Prism.Core/Common/IRegistryAware.cs similarity index 100% rename from src/Maui/Prism.Maui/Common/IRegistryAware.cs rename to src/Prism.Core/Common/IRegistryAware.cs diff --git a/src/Prism.Core/Mvvm/IViewRegistry.cs b/src/Prism.Core/Mvvm/IViewRegistry.cs new file mode 100644 index 0000000000..7770cff581 --- /dev/null +++ b/src/Prism.Core/Mvvm/IViewRegistry.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Prism.Ioc; + +namespace Prism.Mvvm; + +/// +/// Provides an abstraction layer for ViewRegistration that can be mocked +/// +public interface IViewRegistry +{ + /// + /// The existing ViewRegistrations + /// + IEnumerable Registrations { get; } + + /// + /// Creates a view given a specified instance of the Container and a navigation name + /// + /// + /// + /// + object CreateView(IContainerProvider container, string name); + + /// + /// Gets the ViewType for a specified navigation name + /// + /// + /// + Type GetViewType(string name); + + /// + /// Gets the navigation name for a specified ViewModelType + /// + /// + /// + string GetViewModelNavigationKey(Type viewModelType); + + /// + /// Gets the Registrations where the View is of a given base type + /// + /// + /// + IEnumerable ViewsOfType(Type baseType); + + /// + /// Confirms whether the given navigation name has been registered + /// + /// + /// + bool IsRegistered(string name); +} diff --git a/src/Maui/Prism.Maui/Mvvm/ViewCreationException.cs b/src/Prism.Core/Mvvm/ViewCreationException.cs similarity index 95% rename from src/Maui/Prism.Maui/Mvvm/ViewCreationException.cs rename to src/Prism.Core/Mvvm/ViewCreationException.cs index 01180a2aab..f9afbe9c25 100644 --- a/src/Maui/Prism.Maui/Mvvm/ViewCreationException.cs +++ b/src/Prism.Core/Mvvm/ViewCreationException.cs @@ -1,4 +1,4 @@ -using Prism.Common; +using System; namespace Prism.Mvvm; diff --git a/src/Prism.Core/Mvvm/ViewModelCreationException.cs b/src/Prism.Core/Mvvm/ViewModelCreationException.cs new file mode 100644 index 0000000000..45f2573906 --- /dev/null +++ b/src/Prism.Core/Mvvm/ViewModelCreationException.cs @@ -0,0 +1,24 @@ +using System; +using System.ComponentModel; + +namespace Prism.Mvvm; + +public class ViewModelCreationException : Exception +{ + private static Func _viewNameDelegate = null; + private static string GetViewName(object view) => _viewNameDelegate is null ? "Platform not initialized" : _viewNameDelegate(view); + + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetViewNameDelegate(Func viewNameDelegate) => _viewNameDelegate = viewNameDelegate; + + public ViewModelCreationException(object view, Exception innerException) + : base($"Unable to Create ViewModel for '{view.GetType().FullName}'.", innerException) + { + View = view; + ViewName = GetViewName(view); + } + + public string ViewName { get; } + + public object View { get; } +} diff --git a/src/Maui/Prism.Maui/Common/ViewRegistration.cs b/src/Prism.Core/Mvvm/ViewRegistration.cs similarity index 82% rename from src/Maui/Prism.Maui/Common/ViewRegistration.cs rename to src/Prism.Core/Mvvm/ViewRegistration.cs index f59de8adb3..7f07887b5a 100644 --- a/src/Maui/Prism.Maui/Common/ViewRegistration.cs +++ b/src/Prism.Core/Mvvm/ViewRegistration.cs @@ -1,4 +1,6 @@ -namespace Prism.Common; +using System; + +namespace Prism.Mvvm; public record ViewRegistration { diff --git a/src/Prism.Core/Mvvm/ViewRegistryBase{TBaseView}.cs b/src/Prism.Core/Mvvm/ViewRegistryBase{TBaseView}.cs new file mode 100644 index 0000000000..d5872923ea --- /dev/null +++ b/src/Prism.Core/Mvvm/ViewRegistryBase{TBaseView}.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using Prism.Ioc; + +namespace Prism.Mvvm; + +public abstract class ViewRegistryBase : IViewRegistry + where TBaseView : class +{ + 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 TBaseView; + SetNavigationNameProperty(view, registration.Name); + //; + + //; + SetContainerProvider(view, container); + ConfigureView(view, container); + + if (registration.ViewModel is not null) + SetViewModelProperty(view, registration.ViewModel); + // + + Autowire(view); + + return view; + } + catch (KeyNotFoundException) + { + throw; + } + catch (ViewModelCreationException) + { + throw; + } + catch (Exception ex) + { + throw new ViewCreationException(name, _registryType, 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 || baseType.IsAssignableFrom(x.View)); + + 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(TBaseView view, IContainerProvider container); + + /// + /// Calls the platform code to Autowire the View if it does not have a ViewModel already + /// + /// + protected abstract void Autowire(TBaseView view); + + /// + /// Sets the specified navigation name that was used to Navigate. This can be useful for back navigation + /// + /// + /// + protected abstract void SetNavigationNameProperty(TBaseView view, string name); + + /// + /// Sets the ViewModel Type to resolve + /// + /// + /// + protected abstract void SetViewModelProperty(TBaseView view, Type viewModelType); + + /// + /// Sets the IContainerProvider making it easier to access on the given View + /// + /// + /// + protected abstract void SetContainerProvider(TBaseView view, IContainerProvider container); + + //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/Maui/Prism.Maui/Common/ViewType.cs b/src/Prism.Core/Mvvm/ViewType.cs similarity index 73% rename from src/Maui/Prism.Maui/Common/ViewType.cs rename to src/Prism.Core/Mvvm/ViewType.cs index a8a68a587f..a1a267f3d0 100644 --- a/src/Maui/Prism.Maui/Common/ViewType.cs +++ b/src/Prism.Core/Mvvm/ViewType.cs @@ -1,4 +1,4 @@ -namespace Prism.Common; +namespace Prism.Mvvm; public enum ViewType { diff --git a/src/Prism.Core/Properties/IsExternalInit.cs b/src/Prism.Core/Properties/IsExternalInit.cs new file mode 100644 index 0000000000..9a846fd09f --- /dev/null +++ b/src/Prism.Core/Properties/IsExternalInit.cs @@ -0,0 +1,10 @@ +using System; +using System.Linq; + +#if NETSTANDARD || NET47 +namespace System.Runtime.CompilerServices; + +internal static class IsExternalInit +{ +} +#endif diff --git a/tests/Maui/Prism.Maui.Tests/Mocks/Ioc/TestContainer.cs b/tests/Maui/Prism.Maui.Tests/Mocks/Ioc/TestContainer.cs index 0e6253f28b..7f3121924e 100644 --- a/tests/Maui/Prism.Maui.Tests/Mocks/Ioc/TestContainer.cs +++ b/tests/Maui/Prism.Maui.Tests/Mocks/Ioc/TestContainer.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Prism.Common; namespace Prism.Maui.Tests.Mocks.Ioc;