Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immutable IoC container for Prism.Autofac.Forms - resolution to issue # 969 #1017

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -41,28 +39,26 @@ protected override void ConfigureModuleCatalog()

protected override void RegisterTypes()
{
var builder = new ContainerBuilder();

builder.RegisterType<ServiceMock>().As<IServiceMock>();
builder.RegisterType<AutowireViewModel>();
builder.RegisterType<ViewModelAMock>();
builder.Register(ctx => new ViewModelBMock()).Named<ViewModelBMock>(ViewModelBMock.Key);
builder.RegisterType<ConstructorArgumentViewModel>();
builder.RegisterType<ModuleMock>().SingleInstance();
FormsDependencyService.Register<IDependencyServiceMock>(new DependencyServiceMock());

builder.Update(Container);
Container.RegisterType<ServiceMock>().As<IServiceMock>();
Container.RegisterType<AutowireViewModel>();
Container.RegisterType<ViewModelAMock>();
Container.Register(ctx => new ViewModelBMock()).Named<ViewModelBMock>(ViewModelBMock.Key);
Container.RegisterType<ConstructorArgumentViewModel>();
Container.RegisterType<ModuleMock>().SingleInstance();
Container.Register(ctx => FormsDependencyService.Get<IDependencyServiceMock>())
.As<IDependencyServiceMock>();

Container.RegisterTypeForNavigation<ViewMock>("view");
Container.RegisterTypeForNavigation<ViewAMock, ViewModelAMock>();
Container.RegisterTypeForNavigation<AutowireView, AutowireViewModel>();
Container.RegisterTypeForNavigation<ConstructorArgumentView, ConstructorArgumentViewModel>();

FormsDependencyService.Register<IDependencyServiceMock>(new DependencyServiceMock());
}

public INavigationService CreateNavigationServiceForPage()
{
return CreateNavigationService();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<ComponentNotRegisteredException>(() => app.Container.Resolve<IDependencyServiceMock>());
//var service = app.Container.Resolve<IDependencyServiceMock>();
//Assert.NotNull(service);
//Assert.IsType<DependencyServiceMock>(service);

//Was: Autofac needs to be updated to support resolving unknown interfaces by using the DependencyService
//Assert.Throws<ComponentNotRegisteredException>(() => app.Container.Resolve<IDependencyServiceMock>());

// 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<IDependencyServiceMock>();
Assert.NotNull(service);
Assert.IsType<DependencyServiceMock>(service);
}

[Fact]
Expand Down Expand Up @@ -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<InvalidOperationException>(() => app.Container.Register(ctx => new List<string>())
.As<IList<string>>());
//But type resolution will work properly
Assert.NotNull(app.Container.Resolve<ViewModelAMock>());
}
}
}
35 changes: 24 additions & 11 deletions Source/Xamarin/Prism.Autofac.Forms/AutofacExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ public static IContainer RegisterTypeForNavigation<TView, TViewModel>(this ICont
/// <param name="otherView">Other Platform Specific View Type</param>
/// <param name="windowsView">Windows Specific View Type</param>
/// <param name="winPhoneView">Windows Phone Specific View Type</param>
/// <returns><see cref="IUnityContainer"/></returns>
/// <returns><see cref="IContainer"/></returns>
// ReSharper disable once InconsistentNaming
public static IContainer RegisterTypeForNavigationOnPlatform<TView, TViewModel>(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
Expand Down Expand Up @@ -109,7 +110,7 @@ public static IContainer RegisterTypeForNavigationOnPlatform<TView, TViewModel>(
/// <param name="desktopView">Desktop Specific View Type</param>
/// <param name="tabletView">Tablet Specific View Type</param>
/// <param name="phoneView">Phone Specific View Type</param>
/// <returns><see cref="IUnityContainer"/></returns>
/// <returns><see cref="IContainer"/></returns>
public static IContainer RegisterTypeForNavigationOnIdiom<TView, TViewModel>(this IContainer container, string name = null, Type desktopView = null, Type tabletView = null, Type phoneView = null)
where TView : Page
where TViewModel : class
Expand Down Expand Up @@ -151,6 +152,7 @@ private static IContainer RegisterTypeForNavigationWithViewModel<TViewModel>(thi
/// after the container is already created.
/// Uses a new ContainerBuilder instance to update the Container.
/// </summary>
/// <param name="container">The container to register type with</param>
/// <param name="type">The type to register.</param>
/// <param name="name">The name you will use to resolve the component in future.</param>
/// <param name="registerAsSingleton">Registers the type as a singleton.</param>
Expand All @@ -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<Page>(name).SingleInstance();
else
containerUpdater.RegisterType(type).Named<Page>(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<Page>(name).SingleInstance();
//With conditional registration in Autofac 4.4.0 and higher, it will look something like this:
//afContainer.Builder.RegisterType(type).Named<Page>(name).SingleInstance().IfNotRegistered(type);
}
else
{
afContainer.RegisterType(type).Named<Page>(name);
//With conditional registration in Autofac 4.4.0 and higher, it will look something like this:
//afContainer.Builder.RegisterType(type).Named<Page>(name).IfNotRegistered(type);
}
}
}
}
}
Expand Down
6 changes: 2 additions & 4 deletions Source/Xamarin/Prism.Autofac.Forms/IPlatformInitializer.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using Autofac;

namespace Prism.Autofac.Forms
namespace Prism.Autofac.Forms
{
public interface IPlatformInitializer : IPlatformInitializer<IContainer>
public interface IPlatformInitializer : IPlatformInitializer<IAutofacContainer>
{
}
}
Loading