From ec40af9d0e1e5881f30b61a1ad97dfc87073414b Mon Sep 17 00:00:00 2001 From: Jeremy Ellis Date: Thu, 13 Apr 2017 12:17:43 -0500 Subject: [PATCH] Immutable container implementation of Prism.Autofac.Forms - with updates to unit tests. --- .../Mocks/PrismApplicationMock.cs | 28 +- .../PrismApplicationFixture.cs | 36 +- .../Prism.Autofac.Forms/AutofacExtensions.cs | 35 +- .../IPlatformInitializer.cs | 6 +- .../Immutable/AutofacContainer.cs | 360 ++++++++++++++++++ .../Immutable/IAutofacContainer.cs | 195 ++++++++++ .../Immutable/IPreRegisterTypes.cs | 16 + .../Immutable/RuntimeComponentRegistry.cs | 110 ++++++ .../Modularity/AutofacModuleInitializer.cs | 17 +- .../AutofacPageNavigationService.cs | 12 +- .../Prism.Autofac.Forms.csproj | 4 + .../Prism.Autofac.Forms/PrismApplication.cs | 128 +++++-- 12 files changed, 859 insertions(+), 88 deletions(-) create mode 100644 Source/Xamarin/Prism.Autofac.Forms/Immutable/AutofacContainer.cs create mode 100644 Source/Xamarin/Prism.Autofac.Forms/Immutable/IAutofacContainer.cs create mode 100644 Source/Xamarin/Prism.Autofac.Forms/Immutable/IPreRegisterTypes.cs create mode 100644 Source/Xamarin/Prism.Autofac.Forms/Immutable/RuntimeComponentRegistry.cs diff --git a/Source/Xamarin/Prism.Autofac.Forms.Tests/Mocks/PrismApplicationMock.cs b/Source/Xamarin/Prism.Autofac.Forms.Tests/Mocks/PrismApplicationMock.cs index 813e016cc2..ec1211f639 100644 --- a/Source/Xamarin/Prism.Autofac.Forms.Tests/Mocks/PrismApplicationMock.cs +++ b/Source/Xamarin/Prism.Autofac.Forms.Tests/Mocks/PrismApplicationMock.cs @@ -5,17 +5,15 @@ using Prism.Modularity; using Prism.Navigation; using Xamarin.Forms; -using Autofac; namespace Prism.Autofac.Forms.Tests.Mocks { public class PrismApplicationMock : PrismApplication { public PrismApplicationMock() - { - } + { } - public PrismApplicationMock(Page startPage) : this() + public PrismApplicationMock(Page startPage) { Current.MainPage = startPage; } @@ -41,23 +39,21 @@ protected override void ConfigureModuleCatalog() protected override void RegisterTypes() { - var builder = new ContainerBuilder(); - - builder.RegisterType().As(); - builder.RegisterType(); - builder.RegisterType(); - builder.Register(ctx => new ViewModelBMock()).Named(ViewModelBMock.Key); - builder.RegisterType(); - builder.RegisterType().SingleInstance(); + FormsDependencyService.Register(new DependencyServiceMock()); - builder.Update(Container); + Container.RegisterType().As(); + Container.RegisterType(); + Container.RegisterType(); + Container.Register(ctx => new ViewModelBMock()).Named(ViewModelBMock.Key); + Container.RegisterType(); + Container.RegisterType().SingleInstance(); + Container.Register(ctx => FormsDependencyService.Get()) + .As(); Container.RegisterTypeForNavigation("view"); Container.RegisterTypeForNavigation(); Container.RegisterTypeForNavigation(); Container.RegisterTypeForNavigation(); - - FormsDependencyService.Register(new DependencyServiceMock()); } public INavigationService CreateNavigationServiceForPage() @@ -65,4 +61,4 @@ public INavigationService CreateNavigationServiceForPage() return CreateNavigationService(); } } -} \ No newline at end of file +} diff --git a/Source/Xamarin/Prism.Autofac.Forms.Tests/PrismApplicationFixture.cs b/Source/Xamarin/Prism.Autofac.Forms.Tests/PrismApplicationFixture.cs index 086fa93860..fd20d0d6d4 100644 --- a/Source/Xamarin/Prism.Autofac.Forms.Tests/PrismApplicationFixture.cs +++ b/Source/Xamarin/Prism.Autofac.Forms.Tests/PrismApplicationFixture.cs @@ -1,5 +1,5 @@ using System; -using System.Reflection; +using System.Collections.Generic; using System.Threading.Tasks; using Prism.Common; using Prism.Autofac.Forms.Tests.Mocks; @@ -12,7 +12,6 @@ using Xamarin.Forms; using Xunit; using Autofac; -using Autofac.Core.Registration; using Prism.DI.Forms.Tests; #if TEST using Application = Prism.FormsApplication; @@ -62,11 +61,15 @@ public void ResolveConcreteTypeNotRegisteredWithContainer() public void ResolveTypeRegisteredWithDependencyService() { var app = new PrismApplicationMock(); - //TODO: Autofac needs to be updated to support resolving unknown interfaces by using the DependencyService - Assert.Throws(() => app.Container.Resolve()); - //var service = app.Container.Resolve(); - //Assert.NotNull(service); - //Assert.IsType(service); + + //Was: Autofac needs to be updated to support resolving unknown interfaces by using the DependencyService + //Assert.Throws(() => app.Container.Resolve()); + + // Update 2017-04-03: Probably don't need this support, since use of DependencyService is being deprecated. + // So added "support" for it by secondarily registering the service directly in the Autofac container. + var service = app.Container.Resolve(); + Assert.NotNull(service); + Assert.IsType(service); } [Fact] @@ -131,5 +134,24 @@ private static INavigationService ResolveAndSetRootPage(PrismApplicationMock app ((IPageAware)navigationService).Page = new ContentPage(); return navigationService; } + + [Fact] + public void Container_IsImmutableAfterBuild() + { + //Arrange + var app = new PrismApplicationMock(); + + //Act + //Referencing the Container's ComponentRegistry causes the Container to be built + // and no further type registration should be possible. + var componentRegistry = app.Container.ComponentRegistry; + + //Assert + Assert.True(app.Container.IsContainerBuilt); + Assert.Throws(() => app.Container.Register(ctx => new List()) + .As>()); + //But type resolution will work properly + Assert.NotNull(app.Container.Resolve()); + } } } \ No newline at end of file diff --git a/Source/Xamarin/Prism.Autofac.Forms/AutofacExtensions.cs b/Source/Xamarin/Prism.Autofac.Forms/AutofacExtensions.cs index d2f60eb931..ea1c4eda5f 100644 --- a/Source/Xamarin/Prism.Autofac.Forms/AutofacExtensions.cs +++ b/Source/Xamarin/Prism.Autofac.Forms/AutofacExtensions.cs @@ -65,7 +65,8 @@ public static IContainer RegisterTypeForNavigation(this ICont /// Other Platform Specific View Type /// Windows Specific View Type /// Windows Phone Specific View Type - /// + /// + // ReSharper disable once InconsistentNaming public static IContainer RegisterTypeForNavigationOnPlatform(this IContainer container, string name = null, Type androidView = null, Type iOSView = null, Type otherView = null, Type windowsView = null, Type winPhoneView = null) where TView : Page where TViewModel : class @@ -109,7 +110,7 @@ public static IContainer RegisterTypeForNavigationOnPlatform( /// Desktop Specific View Type /// Tablet Specific View Type /// Phone Specific View Type - /// + /// public static IContainer RegisterTypeForNavigationOnIdiom(this IContainer container, string name = null, Type desktopView = null, Type tabletView = null, Type phoneView = null) where TView : Page where TViewModel : class @@ -151,6 +152,7 @@ private static IContainer RegisterTypeForNavigationWithViewModel(thi /// after the container is already created. /// Uses a new ContainerBuilder instance to update the Container. /// + /// The container to register type with /// The type to register. /// The name you will use to resolve the component in future. /// Registers the type as a singleton. @@ -162,16 +164,27 @@ private static void RegisterTypeIfMissing(IContainer container, Type type, strin if (name == null) throw new ArgumentNullException(nameof(name)); - if (!container.IsRegistered(type)) + if (container is IAutofacContainer afContainer) { - var containerUpdater = new ContainerBuilder(); - - if (registerAsSingleton) - containerUpdater.RegisterType(type).Named(name).SingleInstance(); - else - containerUpdater.RegisterType(type).Named(name); - - containerUpdater.Update(container); + //TODO: In the future, the use of IsTypeRegistered() should be eliminated and these should be done as conditional + // registrations instead: http://docs.autofac.org/en/latest/register/registration.html#conditional-registration + // But conditional registrations are not available until Autofac 4.4.0, and we are only requiring 3.5.2 at this time + + if (!afContainer.IsTypeRegistered(type)) + { + if (registerAsSingleton) + { + afContainer.RegisterType(type).Named(name).SingleInstance(); + //With conditional registration in Autofac 4.4.0 and higher, it will look something like this: + //afContainer.Builder.RegisterType(type).Named(name).SingleInstance().IfNotRegistered(type); + } + else + { + afContainer.RegisterType(type).Named(name); + //With conditional registration in Autofac 4.4.0 and higher, it will look something like this: + //afContainer.Builder.RegisterType(type).Named(name).IfNotRegistered(type); + } + } } } } diff --git a/Source/Xamarin/Prism.Autofac.Forms/IPlatformInitializer.cs b/Source/Xamarin/Prism.Autofac.Forms/IPlatformInitializer.cs index fbff78090b..d67adcfc1e 100644 --- a/Source/Xamarin/Prism.Autofac.Forms/IPlatformInitializer.cs +++ b/Source/Xamarin/Prism.Autofac.Forms/IPlatformInitializer.cs @@ -1,8 +1,6 @@ -using Autofac; - -namespace Prism.Autofac.Forms +namespace Prism.Autofac.Forms { - public interface IPlatformInitializer : IPlatformInitializer + public interface IPlatformInitializer : IPlatformInitializer { } } diff --git a/Source/Xamarin/Prism.Autofac.Forms/Immutable/AutofacContainer.cs b/Source/Xamarin/Prism.Autofac.Forms/Immutable/AutofacContainer.cs new file mode 100644 index 0000000000..b91ffeb0ee --- /dev/null +++ b/Source/Xamarin/Prism.Autofac.Forms/Immutable/AutofacContainer.cs @@ -0,0 +1,360 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Autofac; +using Autofac.Builder; +using Autofac.Core; +using Autofac.Core.Lifetime; +using Autofac.Core.Resolving; +using Autofac.Features.LightweightAdapters; +using Autofac.Features.OpenGenerics; +using Autofac.Features.Scanning; + +namespace Prism.Autofac.Forms.Immutable +{ + public class AutofacContainer : IAutofacContainer + { + private readonly object _locker = new object(); + + private IContainer _container; + + private ContainerBuilder _builder = new ContainerBuilder(); + + private IComponentRegistry _runtimeRegistry; + + //TODO: The _registeredTypes field should be eliminated when we can require Autofac 4.4.0 or higher + private List _registeredTypes = new List(); + + public bool IsContainerBuilt => _container != null; + + private void CheckBuilder() + { + if (IsContainerBuilt) + { + throw new InvalidOperationException("It is not possible to perform registration operations after the Container has been built."); + } + } + + //TODO: We will be able to eliminate the TrackRegisteredType() method when we can require Autofac 4.4.0 or higher, and do conditional registration. + private void TrackRegisteredType(Type registeredType) + { + if (registeredType == null) return; + lock (_locker) + { + if (!_registeredTypes.Contains(registeredType)) + { + _registeredTypes.Add(registeredType); + } + } + } + + //TODO: We will be able to eliminate the IsTypeRegistered() method when we can require Autofac 4.4.0 or higher, and do conditional registration + [Obsolete("The IsTypeRegistered() method will be removed in the future; if you are using Autofac 4.4.0 (or higher), use conditional registration instead.")] + public bool IsTypeRegistered(Type registeredType) + { + if (registeredType == null) return false; + lock (_locker) + { + return _registeredTypes.Contains(registeredType); + } + } + + private void CheckBuildContainer() + { + if (IsContainerBuilt) return; + lock (_locker) + { + if (_container == null) + { + _container = _builder.Build(); + _runtimeRegistry = new RuntimeComponentRegistry(_container); + } + } + } + + public object ResolveComponent(IComponentRegistration registration, IEnumerable parameters) + { + CheckBuildContainer(); + return _container.ResolveComponent(registration, parameters); + } + + public IComponentRegistry ComponentRegistry + { + get + { + CheckBuildContainer(); + return _runtimeRegistry; + } + } + + #region Registration operations + + /// Add a component to the container. + /// The component to add. + public void RegisterComponent(IComponentRegistration registration) + { + CheckBuilder(); + //TODO: May need to identify the types being registered, and call TrackRegisteredType() on them + _builder.RegisterComponent(registration); + } + + /// Add a registration source to the container. + /// The registration source to add. + public void RegisterSource(IRegistrationSource registrationSource) + { + CheckBuilder(); + //TODO: May need to identify the types being registered, and call TrackRegisteredType() on them + _builder.RegisterSource(registrationSource); + } + + /// Register an instance as a component. + /// The type of the instance. + /// The instance to register. + /// Registration builder allowing the registration to be configured. + /// If no services are explicitly specified for the instance, the + /// static type will be used as the default service (i.e. *not* instance.GetType()). + public IRegistrationBuilder RegisterInstance(T instance) where T : class + { + CheckBuilder(); + TrackRegisteredType(typeof(T)); + return _builder.RegisterInstance(instance); + } + + /// + /// Register a component to be created through reflection. + /// + /// The type of the component implementation. + /// Registration builder allowing the registration to be configured. + public IRegistrationBuilder RegisterType() + { + CheckBuilder(); + TrackRegisteredType(typeof(TImplementer)); + return _builder.RegisterType(); + } + + public IRegistrationBuilder RegisterType(Type implementationType) + { + CheckBuilder(); + TrackRegisteredType(implementationType); + return _builder.RegisterType(implementationType); + } + + /// Register a delegate as a component. + /// The type of the instance. + /// The delegate to register. + /// Registration builder allowing the registration to be configured. + public IRegistrationBuilder Register(Func @delegate) + { + CheckBuilder(); + TrackRegisteredType(typeof(T)); + return _builder.Register(@delegate); + } + + /// Register a delegate as a component. + /// The type of the instance. + /// The delegate to register. + /// Registration builder allowing the registration to be configured. + public IRegistrationBuilder Register(Func, T> @delegate) + { + CheckBuilder(); + TrackRegisteredType(typeof(T)); + return _builder.Register(@delegate); + } + + /// + /// Register an un-parameterised generic type, e.g. Repository<>. + /// Concrete types will be made as they are requested, e.g. with Resolve<Repository<int>>(). + /// + /// The open generic implementation type. + /// Registration builder allowing the registration to be configured. + public IRegistrationBuilder RegisterGeneric(Type implementer) + { + CheckBuilder(); + TrackRegisteredType(implementer); + return _builder.RegisterGeneric(implementer); + } + + /// Register the types in an assembly. + /// The assemblies from which to register types. + /// Registration builder allowing the registration to be configured. + public IRegistrationBuilder RegisterAssemblyTypes(params Assembly[] assemblies) + { + CheckBuilder(); + //TODO: May need to identify the types being registered, and call TrackRegisteredType() on them + return _builder.RegisterAssemblyTypes(assemblies); + } + + /// Register the types in a list. + /// The types to register. + /// Registration builder allowing the registration to be configured. + public IRegistrationBuilder RegisterTypes(params Type[] types) + { + CheckBuilder(); + foreach (Type type in (types ?? new Type[] {})) + { + TrackRegisteredType(type); + } + return _builder.RegisterTypes(types); + } + + /// + /// Adapt all components implementing service + /// to provide using the provided + /// function. + /// + /// Service type to adapt from. + /// Service type to adapt to. Must not be the + /// same as . + /// Function adapting to + /// service , given the context and parameters. + public IRegistrationBuilder RegisterAdapter(Func, TFrom, TTo> adapter) + { + CheckBuilder(); + TrackRegisteredType(typeof(TTo)); + return _builder.RegisterAdapter(adapter); + } + + /// + /// Adapt all components implementing service + /// to provide using the provided + /// function. + /// + /// Service type to adapt from. + /// Service type to adapt to. Must not be the + /// same as . + /// Function adapting to + /// service , given the context. + public IRegistrationBuilder RegisterAdapter(Func adapter) + { + CheckBuilder(); + TrackRegisteredType(typeof(TTo)); + return _builder.RegisterAdapter(adapter); + } + + /// + /// Adapt all components implementing service + /// to provide using the provided + /// function. + /// + /// Service type to adapt from. + /// Service type to adapt to. Must not be the + /// same as . + /// Function adapting to + /// service . + public IRegistrationBuilder RegisterAdapter(Func adapter) + { + CheckBuilder(); + TrackRegisteredType(typeof(TTo)); + return _builder.RegisterAdapter(adapter); + } + + /// + /// Decorate all components implementing open generic service . + /// The and parameters must be different values. + /// + /// Service type being decorated. Must be an open generic type. + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + /// The type of the decorator. Must be an open generic type, and accept a parameter + /// of type , which will be set to the instance being decorated. + public IRegistrationBuilder RegisterGenericDecorator(Type decoratorType, Type decoratedServiceType, object fromKey, object toKey = null) + { + CheckBuilder(); + TrackRegisteredType(decoratedServiceType); + return _builder.RegisterGenericDecorator(decoratorType, decoratedServiceType, fromKey, toKey); + } + + /// + /// Decorate all components implementing service + /// using the provided function. + /// The and parameters must be different values. + /// + /// Service type being decorated. + /// Function decorating a component instance that provides + /// , given the context and parameters. + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + public IRegistrationBuilder RegisterDecorator(Func, TService, TService> decorator, object fromKey, object toKey = null) + { + CheckBuilder(); + TrackRegisteredType(typeof(TService)); + return _builder.RegisterDecorator(decorator, fromKey, toKey); + } + + /// + /// Decorate all components implementing service + /// using the provided function. + /// The and parameters must be different values. + /// + /// Service type being decorated. + /// Function decorating a component instance that provides + /// , given the context. + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + public IRegistrationBuilder RegisterDecorator(Func decorator, object fromKey, object toKey = null) + { + CheckBuilder(); + TrackRegisteredType(typeof(TService)); + return _builder.RegisterDecorator(decorator, fromKey, toKey); + } + + /// + /// Decorate all components implementing service + /// using the provided function. + /// The and parameters must be different values. + /// + /// Service type being decorated. + /// Function decorating a component instance that provides + /// . + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + public IRegistrationBuilder RegisterDecorator(Func decorator, object fromKey, object toKey = null) + { + CheckBuilder(); + TrackRegisteredType(typeof(TService)); + return _builder.RegisterDecorator(decorator, fromKey, toKey); + } + + #endregion + + public void Dispose() + { + _runtimeRegistry?.Dispose(); + _runtimeRegistry = null; + _builder = null; + _container?.Dispose(); + _container = null; + _registeredTypes?.Clear(); + _registeredTypes = null; + } + + public ILifetimeScope BeginLifetimeScope() + { + return _container?.BeginLifetimeScope(); + } + + public ILifetimeScope BeginLifetimeScope(object tag) + { + return _container?.BeginLifetimeScope(tag); + } + + public ILifetimeScope BeginLifetimeScope(Action configurationAction) + { + return _container?.BeginLifetimeScope(configurationAction); + } + + public ILifetimeScope BeginLifetimeScope(object tag, Action configurationAction) + { + return _container?.BeginLifetimeScope(tag, configurationAction); + } + + public IDisposer Disposer => _container?.Disposer; + + public object Tag => _container?.Tag; + + //These events appear to not be used by Prism.Autofac.Forms + public event EventHandler ChildLifetimeScopeBeginning; + public event EventHandler CurrentScopeEnding; + public event EventHandler ResolveOperationBeginning; + } +} diff --git a/Source/Xamarin/Prism.Autofac.Forms/Immutable/IAutofacContainer.cs b/Source/Xamarin/Prism.Autofac.Forms/Immutable/IAutofacContainer.cs new file mode 100644 index 0000000000..2f6675853c --- /dev/null +++ b/Source/Xamarin/Prism.Autofac.Forms/Immutable/IAutofacContainer.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Autofac; +using Autofac.Builder; +using Autofac.Core; +using Autofac.Features.LightweightAdapters; +using Autofac.Features.OpenGenerics; +using Autofac.Features.Scanning; + +// ReSharper disable once CheckNamespace +namespace Prism.Autofac.Forms +{ + /// + /// An abstraction of an Autofac ContainerBuilder plus Container - wraps an Autofac IContainer + /// instance that can only be built once and cannot be updated (i.e. is immutable). + /// + public interface IAutofacContainer : IContainer + { + /// + /// Identifies if the wrapped Autofac IContainer instance has been built or not. + /// (Type/page registrations can no longer be made after it has been built. + /// + bool IsContainerBuilt { get; } + + //TODO: We will be able to eliminate the IsTypeRegistered() method when we can require Autofac 4.4.0 or higher, and do conditional registration + /// + /// Identifies if a particular Type has already been registered for the wrapped IContainer instance (to be built) + /// + /// The Type to check + /// True if the Type is already registered, False if it is not. + [Obsolete("The IsTypeRegistered() method will be removed in the future; if you are using Autofac 4.4.0 (or higher), use conditional registration instead.")] + bool IsTypeRegistered(Type registeredType); + + #region Registration operations + + /// Add a component to the container. + /// The component to add. + void RegisterComponent(IComponentRegistration registration); + + /// Add a registration source to the container. + /// The registration source to add. + void RegisterSource(IRegistrationSource registrationSource); + + /// Register an instance as a component. + /// The type of the instance. + /// The instance to register. + /// Registration builder allowing the registration to be configured. + /// If no services are explicitly specified for the instance, the + /// static type will be used as the default service (i.e. *not* instance.GetType()). + IRegistrationBuilder RegisterInstance(T instance) + where T : class; + + /// + /// Register a component to be created through reflection. + /// + /// The type of the component implementation. + /// Registration builder allowing the registration to be configured. + IRegistrationBuilder + RegisterType(); + + IRegistrationBuilder RegisterType( + Type implementationType); + + /// Register a delegate as a component. + /// The type of the instance. + /// The delegate to register. + /// Registration builder allowing the registration to be configured. + IRegistrationBuilder Register( + Func @delegate); + + /// Register a delegate as a component. + /// The type of the instance. + /// The delegate to register. + /// Registration builder allowing the registration to be configured. + IRegistrationBuilder Register( + Func, T> @delegate); + + /// + /// Register an un-parameterised generic type, e.g. Repository<>. + /// Concrete types will be made as they are requested, e.g. with Resolve<Repository<int>>(). + /// + /// The open generic implementation type. + /// Registration builder allowing the registration to be configured. + IRegistrationBuilder RegisterGeneric( + Type implementer); + + /// Register the types in an assembly. + /// The assemblies from which to register types. + /// Registration builder allowing the registration to be configured. + IRegistrationBuilder RegisterAssemblyTypes( + params Assembly[] assemblies); + + /// Register the types in a list. + /// The types to register. + /// Registration builder allowing the registration to be configured. + IRegistrationBuilder + RegisterTypes(params Type[] types); + + /// + /// Adapt all components implementing service + /// to provide using the provided + /// function. + /// + /// Service type to adapt from. + /// Service type to adapt to. Must not be the + /// same as . + /// Function adapting to + /// service , given the context and parameters. + IRegistrationBuilder + RegisterAdapter(Func, TFrom, TTo> adapter); + + /// + /// Adapt all components implementing service + /// to provide using the provided + /// function. + /// + /// Service type to adapt from. + /// Service type to adapt to. Must not be the + /// same as . + /// Function adapting to + /// service , given the context. + IRegistrationBuilder + RegisterAdapter(Func adapter); + + /// + /// Adapt all components implementing service + /// to provide using the provided + /// function. + /// + /// Service type to adapt from. + /// Service type to adapt to. Must not be the + /// same as . + /// Function adapting to + /// service . + IRegistrationBuilder + RegisterAdapter(Func adapter); + + /// + /// Decorate all components implementing open generic service . + /// The and parameters must be different values. + /// + /// Service type being decorated. Must be an open generic type. + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + /// The type of the decorator. Must be an open generic type, and accept a parameter + /// of type , which will be set to the instance being decorated. + IRegistrationBuilder + RegisterGenericDecorator(Type decoratorType, Type decoratedServiceType, object fromKey, + object toKey = null); + + /// + /// Decorate all components implementing service + /// using the provided function. + /// The and parameters must be different values. + /// + /// Service type being decorated. + /// Function decorating a component instance that provides + /// , given the context and parameters. + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + IRegistrationBuilder + RegisterDecorator(Func, TService, TService> decorator, + object fromKey, object toKey = null); + + /// + /// Decorate all components implementing service + /// using the provided function. + /// The and parameters must be different values. + /// + /// Service type being decorated. + /// Function decorating a component instance that provides + /// , given the context. + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + IRegistrationBuilder + RegisterDecorator(Func decorator, object fromKey, + object toKey = null); + + /// + /// Decorate all components implementing service + /// using the provided function. + /// The and parameters must be different values. + /// + /// Service type being decorated. + /// Function decorating a component instance that provides + /// . + /// Service key or name associated with the components being decorated. + /// Service key or name given to the decorated components. + IRegistrationBuilder + RegisterDecorator(Func decorator, object fromKey, object toKey = null); + + #endregion + } +} diff --git a/Source/Xamarin/Prism.Autofac.Forms/Immutable/IPreRegisterTypes.cs b/Source/Xamarin/Prism.Autofac.Forms/Immutable/IPreRegisterTypes.cs new file mode 100644 index 0000000000..8eea29a474 --- /dev/null +++ b/Source/Xamarin/Prism.Autofac.Forms/Immutable/IPreRegisterTypes.cs @@ -0,0 +1,16 @@ +// ReSharper disable once CheckNamespace +namespace Prism.Autofac.Forms +{ + /// + /// Identifies a class (typically an implementation of IModule) that has Type/Page registration + /// requirements that must be handled, prior to building the Prism Autofac container. + /// + public interface IPreRegisterTypes + { + /// + /// Method that is executed during module cataloging to register any Types/Pages that are required by the module. + /// + /// The container that registration operations will be performed on + void RegisterTypes(IAutofacContainer container); + } +} diff --git a/Source/Xamarin/Prism.Autofac.Forms/Immutable/RuntimeComponentRegistry.cs b/Source/Xamarin/Prism.Autofac.Forms/Immutable/RuntimeComponentRegistry.cs new file mode 100644 index 0000000000..6db7e99af1 --- /dev/null +++ b/Source/Xamarin/Prism.Autofac.Forms/Immutable/RuntimeComponentRegistry.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Autofac; +using Autofac.Core; + +namespace Prism.Autofac.Forms.Immutable +{ + //Newer versions of Autofac (e.g. 4.5.x) require implementations of IComponentRegistry to + // have a readonly Properties property of type IDictionary + internal interface IPropertiesDictionary + { + IDictionary Properties { get; } + } + + /// + /// Implementation of IComponentRegistry that is provided for querying Type/Page registrations after + /// the Prism Autofac container has been built; but does not allow subsequent registrations. + /// + public class RuntimeComponentRegistry : IComponentRegistry, IPropertiesDictionary + { + private IContainer _container; + + public RuntimeComponentRegistry(IContainer container) + { + _container = container ?? throw new ArgumentNullException(nameof(container)); + } + + public bool TryGetRegistration(Service service, out IComponentRegistration registration) + { + return _container.ComponentRegistry.TryGetRegistration(service, out registration); + } + + public bool IsRegistered(Service service) + { + return _container.ComponentRegistry.IsRegistered(service); + } + + public void Register(IComponentRegistration registration) + { + throw new InvalidOperationException("It is not possible to use ContainerBuilder.Update() with an immutable Autofac container; " + + " or to perform registration operations after the container has been built."); + } + + public void Register(IComponentRegistration registration, bool preserveDefaults) + { + throw new InvalidOperationException("It is not possible to use ContainerBuilder.Update() with an immutable Autofac container; " + + " or to perform registration operations after the container has been built."); + } + + public void AddRegistrationSource(IRegistrationSource source) + { + throw new InvalidOperationException("It is not possible to use ContainerBuilder.Update() with an immutable Autofac container; " + + " or to perform registration operations after the container has been built."); + } + + public IEnumerable RegistrationsFor(Service service) + { + return _container.ComponentRegistry.RegistrationsFor(service); + } + + public IEnumerable Registrations => _container.ComponentRegistry.Registrations; + + public IEnumerable Sources => _container.ComponentRegistry.Sources; + + //TODO: The problem here is that IComponentRegistry in Autofac 3.x does NOT have a Properties property + // but Autofac 4.x does. We don't want to require Autofac 4.x at this time, because that requires .NETStandard + // which requires extra work in Xamarin.Forms and Prism projects. So attempting to retrieve the value + // from the _container.ComponentRegistry if available. + // When we get to the point where we can require Autofac 4.x, it will just be this simple line: + //public IDictionary Properties => _container.ComponentRegistry.Properties; + public IDictionary Properties + { + get + { + IDictionary result = null; + + if (_container?.ComponentRegistry != null) + { + try + { + PropertyInfo props = _container.ComponentRegistry.GetType().GetRuntimeProperty("Properties"); + if (props != null) + { + result = props.GetValue(_container.ComponentRegistry, null) as IDictionary; + } + } + catch (Exception) + { + //Could not retrieve Properties property from _container.ComponentRegistry + result = new Dictionary(); + } + } + + return result; + } + } + + public bool HasLocalComponents => _container.ComponentRegistry.HasLocalComponents; + + //These events do not appear to be used by Prism.Autofac.Forms at all. + public event EventHandler Registered; + public event EventHandler RegistrationSourceAdded; + + public void Dispose() + { + _container = null; + } + } +} diff --git a/Source/Xamarin/Prism.Autofac.Forms/Modularity/AutofacModuleInitializer.cs b/Source/Xamarin/Prism.Autofac.Forms/Modularity/AutofacModuleInitializer.cs index 08004bacf5..93c76af8b9 100644 --- a/Source/Xamarin/Prism.Autofac.Forms/Modularity/AutofacModuleInitializer.cs +++ b/Source/Xamarin/Prism.Autofac.Forms/Modularity/AutofacModuleInitializer.cs @@ -6,32 +6,31 @@ namespace Prism.Autofac.Forms.Modularity { public class AutofacModuleInitializer : IModuleInitializer { - readonly IContainer _container; + readonly IContainer _context; /// - /// Create a new instance of with + /// Create a new instance of with /// - /// + /// public AutofacModuleInitializer(IContainer context) { - _container = context; + _context = context; } public void Initialize(ModuleInfo moduleInfo) { - var module = (IModule)_container.Resolve(moduleInfo.ModuleType); - if (module != null) - module.Initialize(); + var module = (IModule)_context.Resolve(moduleInfo.ModuleType); + module?.Initialize(); } /// - /// Create the for by resolving from + /// Create the for by resolving from /// /// Type of module to create /// An isntance of for if exists; otherwise protected virtual IModule CreateModule(Type moduleType) { - return _container.Resolve(moduleType) as IModule; + return _context.Resolve(moduleType) as IModule; } } } diff --git a/Source/Xamarin/Prism.Autofac.Forms/Navigation/AutofacPageNavigationService.cs b/Source/Xamarin/Prism.Autofac.Forms/Navigation/AutofacPageNavigationService.cs index 6e67efcb76..69b9b949be 100644 --- a/Source/Xamarin/Prism.Autofac.Forms/Navigation/AutofacPageNavigationService.cs +++ b/Source/Xamarin/Prism.Autofac.Forms/Navigation/AutofacPageNavigationService.cs @@ -5,6 +5,7 @@ using Prism.Navigation; using Xamarin.Forms; +// ReSharper disable once CheckNamespace namespace Prism.Autofac.Navigation { /// @@ -12,7 +13,7 @@ namespace Prism.Autofac.Navigation /// public class AutofacPageNavigationService : PageNavigationService { - readonly IContainer _container; + private IContainer _container; /// /// Create a new instance of with @@ -26,10 +27,15 @@ public AutofacPageNavigationService(IContainer container, IApplicationProvider a _container = container; } + internal void SetContainer(IContainer container) + { + _container = container; + } + /// - /// Resolve a from for + /// Resolve a from for /// - /// Page to resolve + /// Page to resolve /// A protected override Page CreatePage(string name) { diff --git a/Source/Xamarin/Prism.Autofac.Forms/Prism.Autofac.Forms.csproj b/Source/Xamarin/Prism.Autofac.Forms/Prism.Autofac.Forms.csproj index f57d68ad38..beb9135960 100644 --- a/Source/Xamarin/Prism.Autofac.Forms/Prism.Autofac.Forms.csproj +++ b/Source/Xamarin/Prism.Autofac.Forms/Prism.Autofac.Forms.csproj @@ -46,6 +46,10 @@ + + + + diff --git a/Source/Xamarin/Prism.Autofac.Forms/PrismApplication.cs b/Source/Xamarin/Prism.Autofac.Forms/PrismApplication.cs index 63f8f8d6d1..2ca5e5c005 100644 --- a/Source/Xamarin/Prism.Autofac.Forms/PrismApplication.cs +++ b/Source/Xamarin/Prism.Autofac.Forms/PrismApplication.cs @@ -1,4 +1,6 @@ -using System.Linq; +using System; +using System.Linq; +using System.Reflection; using Prism.Navigation; using Prism.Mvvm; using Prism.Common; @@ -14,25 +16,33 @@ using Prism.Autofac.Navigation; using Prism.Autofac.Forms; using Prism.AppModel; +using Prism.Autofac.Forms.Immutable; +// ReSharper disable once CheckNamespace namespace Prism.Autofac { /// /// Application base class using Autofac /// - public abstract class PrismApplication : PrismApplicationBase + public abstract class PrismApplication : PrismApplicationBase { /// /// Service key used when registering the with the container /// + // ReSharper disable once InconsistentNaming const string _navigationServiceName = "AutofacPageNavigationService"; + private IAutofacContainer _immutableContainer; + private IApplicationProvider _immutableApplicationProvider; + private INavigationService _initialNavigationService; + private bool _doModuleManagerRun; + /// /// Create a new instance of /// - /// Class to initialize platform instances + /// Class to initialize platform instances /// - /// The method will be called after + /// The method will be called after /// to allow for registering platform specific instances. /// protected PrismApplication(IPlatformInitializer initializer = null) @@ -43,7 +53,6 @@ protected PrismApplication(IPlatformInitializer initializer = null) public override void Initialize() { base.Initialize(); - FinishContainerConfiguration(); } @@ -52,8 +61,7 @@ protected override void ConfigureViewModelLocator() ViewModelLocationProvider.SetDefaultViewModelFactory((view, type) => { NamedParameter parameter = null; - var page = view as Page; - if (page != null) + if (view is Page page) { parameter = new NamedParameter("navigationService", CreateNavigationService(page)); } @@ -63,12 +71,12 @@ protected override void ConfigureViewModelLocator() } /// - /// Create a default instance of + /// Create a default instance of /// - /// An instance of - protected override IContainer CreateContainer() + /// An instance of + protected override IAutofacContainer CreateContainer() { - return new ContainerBuilder().Build(); + return (_immutableContainer = _immutableContainer ?? new AutofacContainer()); } protected override IModuleManager CreateModuleManager() @@ -80,41 +88,75 @@ protected override IModuleManager CreateModuleManager() /// Create instance of /// /// - /// The is used as service key when resolving + /// The is used as service key when resolving /// /// Instance of protected override INavigationService CreateNavigationService() { - return Container.ResolveNamed(_navigationServiceName); + return (Container.IsContainerBuilt) + ? Container.ResolveNamed(_navigationServiceName) + : _initialNavigationService; } protected override void InitializeModules() { - if (ModuleCatalog.Modules.Any()) - { - var manager = Container.Resolve(); - manager.Run(); - } + //In immutable mode, module initialization is moved to the FinishContainerConfiguration() method + _doModuleManagerRun = ModuleCatalog.Modules.Any(); } protected override void ConfigureContainer() { - var builder = new ContainerBuilder(); - - builder.RegisterInstance(Logger).As().SingleInstance(); - builder.RegisterInstance(ModuleCatalog).As().SingleInstance(); - - builder.Register(ctx => new ApplicationProvider()).As().SingleInstance(); - builder.Register(ctx => new ApplicationStore()).As().SingleInstance(); - builder.Register(ctx => new AutofacPageNavigationService(Container, Container.Resolve(), Container.Resolve())).Named(_navigationServiceName); - builder.Register(ctx => new ModuleManager(Container.Resolve(), Container.Resolve())).As().SingleInstance(); - builder.Register(ctx => new AutofacModuleInitializer(Container)).As().SingleInstance(); - builder.Register(ctx => new EventAggregator()).As().SingleInstance(); - builder.Register(ctx => new DependencyService()).As().SingleInstance(); - builder.Register(ctx => new PageDialogService(ctx.Resolve())).As().SingleInstance(); - builder.Register(ctx => new DeviceService()).As().SingleInstance(); - - builder.Update(Container); + _immutableApplicationProvider = _immutableApplicationProvider ?? new ApplicationProvider(); + _initialNavigationService = _initialNavigationService ?? + new AutofacPageNavigationService(null, _immutableApplicationProvider, Logger); + + Container.RegisterInstance(Logger).As().SingleInstance(); + Container.RegisterInstance(ModuleCatalog).As().SingleInstance(); + Container.RegisterInstance(_immutableApplicationProvider).As().SingleInstance(); + Container.Register(ctx => new ApplicationStore()).As().SingleInstance(); + Container.Register(ctx => new AutofacPageNavigationService(Container, Container.Resolve(), Container.Resolve())) + .Named(_navigationServiceName); + Container.Register(ctx => new AutofacModuleInitializer(Container)).As().SingleInstance(); + Container.Register(ctx => new ModuleManager(Container.Resolve(), Container.Resolve())) + .As().SingleInstance(); + Container.Register(ctx => new EventAggregator()).As().SingleInstance(); + Container.Register(ctx => new DependencyService()).As().SingleInstance(); + Container.Register(ctx => new PageDialogService(ctx.Resolve())).As().SingleInstance(); + Container.Register(ctx => new DeviceService()).As().SingleInstance(); + Container.RegisterInstance(Container).As().SingleInstance(); + Container.RegisterInstance(Container).As().SingleInstance(); + Container.Register(ctx => CreateNavigationService()).As(); + } + + private void PreRegisterModuleTypes() + { + foreach (Type moduleType in ModuleCatalog + .Modules + .Select(s => s.ModuleType) + .Where(w => w != null && w.GetTypeInfo().ImplementedInterfaces.Contains(typeof(IPreRegisterTypes))) + .Distinct()) + { + object instance = null; + foreach (ConstructorInfo ctor in moduleType.GetTypeInfo().DeclaredConstructors) + { + ParameterInfo[] ctorParams = ctor.GetParameters(); + if (ctorParams == null || ctorParams.Length == 0) + { + instance = ctor.Invoke(new object[] { }); + } + else if (ctorParams.Length == 1 && (ctorParams[0].ParameterType == typeof(IContainer) || + ctorParams[0].ParameterType == typeof(IAutofacContainer))) + { + instance = ctor.Invoke(new object[] { Container }); + } + } + if (instance == null) + { + throw new InvalidOperationException( + $"Unable to execute RegisterTypes() on the '{moduleType.Name}' module because a compatible constructor could not be found."); + } + (instance as IPreRegisterTypes)?.RegisterTypes(Container); + } } /// @@ -122,11 +164,21 @@ protected override void ConfigureContainer() /// private void FinishContainerConfiguration() { - var containerUpdater = new ContainerBuilder(); + if (_doModuleManagerRun) + { + //Pre-registering any module types here - using reflection to create an instance of the module and run RegisterTypes() on it + // because the container has not been built yet; so I can't use the container to give me an instance of the module. + PreRegisterModuleTypes(); + } + + Container.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()); + (_initialNavigationService as AutofacPageNavigationService)?.SetContainer(Container); - // Make sure any not specifically registered concrete type can resolve. - containerUpdater.RegisterSource(new AnyConcreteTypeNotAlreadyRegisteredSource()); - containerUpdater.Update(Container); + if (_doModuleManagerRun) + { + //Finished registering things in the container, so the container can be built and modules initialized + Container.Resolve().Run(); + } } } }