Change how ViewManager is configured

Instead of taking a reference to the Bootstrapper, it takes a configuration
object which is populated by the Bootrapper. This will pave the way for
easier views-in-multiple-assemblies handling in the near future...

This also involved some work to remove registering Stylet's assembly with
the IoC container and the ViewManager. It wasn't really necessary
anyway and just slowed things down.
This commit is contained in:
Antony Male 2015-09-24 17:48:40 +01:00
parent 7fe21eb40b
commit 1731f2388a
15 changed files with 144 additions and 79 deletions

View File

@ -1,7 +1,9 @@
using Autofac;
using Stylet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Windows;
namespace Bootstrappers
@ -29,13 +31,18 @@ namespace Bootstrappers
/// </summary>
protected virtual void DefaultConfigureIoC(ContainerBuilder builder)
{
builder.RegisterInstance<IViewManagerConfig>(this);
var viewManagerConfig = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly },
ViewFactory = this.GetInstance,
};
builder.RegisterInstance<ViewManagerConfig>(viewManagerConfig);
builder.RegisterType<ViewManager>().As<IViewManager>().SingleInstance();
builder.RegisterInstance<IWindowManagerConfig>(this);
builder.RegisterType<WindowManager>().As<IWindowManager>().SingleInstance();
builder.RegisterType<EventAggregator>().As<IEventAggregator>().SingleInstance();
builder.RegisterType<MessageBoxViewModel>().As<IMessageBoxViewModel>(); // Not singleton!
builder.RegisterAssemblyTypes(this.Assemblies.ToArray());
builder.RegisterAssemblyTypes(this.GetType().Assembly);
}
/// <summary>

View File

@ -82,6 +82,7 @@
<Compile Include="Tests\NinjectTests.cs" />
<Compile Include="Tests\NoIoCContainerTests.cs" />
<Compile Include="Tests\StructureMapTests.cs" />
<Compile Include="Tests\StubType.cs" />
<Compile Include="Tests\UnityTests.cs" />
<Compile Include="UnityBootstrapper.cs" />
</ItemGroup>

View File

@ -3,6 +3,8 @@ using Castle.MicroKernel.Registration;
using Castle.Windsor;
using Stylet;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
namespace Bootstrappers
@ -30,17 +32,20 @@ namespace Bootstrappers
protected virtual void DefaultConfigureIoC(IWindsorContainer container)
{
container.AddFacility<TypedFactoryFacility>();
var viewManagerConfig = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly },
ViewFactory = this.GetInstance,
};
container.Register(
Component.For<IViewManagerConfig, IWindowManagerConfig>().Instance(this),
Component.For<ViewManagerConfig>().Instance(viewManagerConfig),
Component.For<IWindowManagerConfig>().Instance(this),
Component.For<IViewManager>().ImplementedBy<ViewManager>().LifestyleSingleton(),
Component.For<IWindowManager>().ImplementedBy<WindowManager>().LifestyleSingleton(),
Component.For<IEventAggregator>().ImplementedBy<EventAggregator>().LifestyleSingleton(),
Component.For<IMessageBoxViewModel>().ImplementedBy<MessageBoxViewModel>().LifestyleTransient()
);
foreach (var assembly in this.Assemblies)
{
container.Register(Classes.FromAssembly(assembly).Pick().LifestyleTransient());
}
container.Register(Classes.FromAssembly(this.GetType().Assembly).Pick().LifestyleTransient());
}
/// <summary>

View File

@ -1,6 +1,8 @@
using Ninject;
using Stylet;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
namespace Bootstrappers
@ -27,7 +29,12 @@ namespace Bootstrappers
/// </summary>
protected virtual void DefaultConfigureIoC(IKernel kernel)
{
kernel.Bind<IViewManagerConfig>().ToConstant(this);
var viewManagerConfig = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly },
ViewFactory = this.GetInstance,
};
kernel.Bind<ViewManagerConfig>().ToConstant(viewManagerConfig);
kernel.Bind<IViewManager>().To<ViewManager>().InSingletonScope();
kernel.Bind<IWindowManagerConfig>().ToConstant(this);
kernel.Bind<IWindowManager>().ToMethod(c => new WindowManager(c.Kernel.Get<IViewManager>(), () => c.Kernel.Get<IMessageBoxViewModel>(), c.Kernel.Get<IWindowManagerConfig>())).InSingletonScope();

View File

@ -1,6 +1,7 @@
using Stylet;
using System;
using System.Collections.Generic;
using System.Reflection;
namespace Bootstrappers
{
@ -16,7 +17,12 @@ namespace Bootstrappers
protected virtual void DefaultConfigureContainer()
{
var viewManager = new ViewManager(this);
var viewManagerConfig = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly },
ViewFactory = this.GetInstance,
};
var viewManager = new ViewManager(viewManagerConfig);
this.Container.Add(typeof(IViewManager), () => viewManager);
var windowManager = new WindowManager(viewManager, () => (IMessageBoxViewModel)this.Container[typeof(IMessageBoxViewModel)](), this);

View File

@ -2,6 +2,8 @@
using StructureMap.Pipeline;
using Stylet;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
namespace Bootstrappers
@ -30,16 +32,18 @@ namespace Bootstrappers
/// </summary>
protected virtual void DefaultConfigureIoC(ConfigurationExpression config)
{
config.For<IViewManagerConfig>().Add(this);
var viewManagerConfig = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly },
ViewFactory = this.GetInstance,
};
config.For<ViewManagerConfig>().Add(viewManagerConfig);
config.For<IViewManager>().Add<ViewManager>().LifecycleIs<SingletonLifecycle>();
config.For<IWindowManagerConfig>().Add(this);
config.For<IWindowManager>().Add<WindowManager>().LifecycleIs<SingletonLifecycle>();
config.For<IEventAggregator>().Add<EventAggregator>().LifecycleIs<SingletonLifecycle>();
config.For<IMessageBoxViewModel>().Add<MessageBoxViewModel>().LifecycleIs<UniquePerRequestLifecycle>();
foreach (var assembly in this.Assemblies)
{
config.Scan(x => x.Assembly(assembly));
}
config.Scan(x => x.Assembly(this.GetType().Assembly));
}
/// <summary>

View File

@ -34,7 +34,7 @@ namespace Bootstrappers.Tests
[Test]
public void CallsConfiguredInCorrectOrder()
{
Assert.That(this.bootstrapper.ConfigureLog, Is.EquivalentTo(new[] { "Configure", "DefaultConfigureIoC", "ConfigureIoC" }));
Assert.That(this.bootstrapper.ConfigureLog, Is.EquivalentTo(new[] { "DefaultConfigureIoC", "ConfigureIoC" }));
}
[Test]
@ -118,9 +118,9 @@ namespace Bootstrappers.Tests
Assert.Ignore("Autobinding not supported");
// Pick a random class with no dependencies...
Assert.DoesNotThrow(() => this.bootstrapper.GetInstance(typeof(StyletIoC.StyletIoCBuilder)));
var vm1 = this.bootstrapper.GetInstance(typeof(StyletIoC.StyletIoCBuilder));
var vm2 = this.bootstrapper.GetInstance(typeof(StyletIoC.StyletIoCBuilder));
Assert.DoesNotThrow(() => this.bootstrapper.GetInstance(typeof(StubType)));
var vm1 = this.bootstrapper.GetInstance(typeof(StubType));
var vm2 = this.bootstrapper.GetInstance(typeof(StubType));
Assert.NotNull(vm1);
Assert.AreNotEqual(vm1, vm2);

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Bootstrappers.Tests
{
public class StubType
{
}
}

View File

@ -1,6 +1,8 @@
using Microsoft.Practices.Unity;
using Stylet;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
namespace Bootstrappers
@ -27,14 +29,19 @@ namespace Bootstrappers
/// </summary>
protected virtual void DefaultConfigureIoC(IUnityContainer container)
{
// For some reason using ContainerControlledLifetimeManager results in a transient registration....
// This is a workaround
var viewManager = new ViewManager(this);
var viewManagerConfig = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly },
ViewFactory = this.GetInstance,
};
// For some reason using ContainerControlledLifetimeManager results in a transient registration....
var viewManager = new ViewManager(viewManagerConfig);
container.RegisterInstance<IViewManager>(viewManager);
container.RegisterInstance<IWindowManager>(new WindowManager(viewManager, () => container.Resolve<IMessageBoxViewModel>(), this));
container.RegisterInstance<IEventAggregator>(new EventAggregator());
container.RegisterType<IMessageBoxViewModel, MessageBoxViewModel>(new PerResolveLifetimeManager());
container.RegisterTypes(AllClasses.FromAssemblies(this.Assemblies), WithMappings.None, WithName.Default, WithLifetime.PerResolve);
container.RegisterTypes(AllClasses.FromAssemblies(this.GetType().Assembly), WithMappings.None, WithName.Default, WithLifetime.PerResolve);
}
/// <summary>

View File

@ -27,10 +27,10 @@ namespace Stylet.Samples.OverridingViewManager
// Dictionary of ViewModel type -> View type
private readonly Dictionary<Type, Type> viewModelToViewMapping;
public CustomViewManager(IViewManagerConfig config)
public CustomViewManager(ViewManagerConfig config)
: base(config)
{
var mappings = from type in this.Assemblies.SelectMany(x => x.GetTypes())
var mappings = from type in this.ViewsAssembly.GetExportedTypes()
let attribute = type.GetCustomAttribute<ViewModelAttribute>()
where attribute != null && typeof(UIElement).IsAssignableFrom(type)
select new { View = type, ViewModel = attribute.ViewModel };

View File

@ -32,7 +32,7 @@ namespace Stylet
protected override sealed void ConfigureBootstrapper()
{
var builder = new StyletIoCBuilder();
builder.Assemblies = new List<Assembly>(this.Assemblies);
builder.Assemblies = new List<Assembly>(new List<Assembly>() { this.GetType().Assembly });
// Call DefaultConfigureIoC *after* ConfigureIoIC, so that they can customize builder.Assemblies
this.ConfigureIoC(builder);
@ -48,7 +48,12 @@ namespace Stylet
protected virtual void DefaultConfigureIoC(StyletIoCBuilder builder)
{
// Mark these as auto-bindings, so the user can replace them if they want
builder.Bind<IViewManagerConfig>().ToInstance(this).AsWeakBinding();
var viewManagerConfig = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { this.GetType().Assembly },
ViewFactory = this.GetInstance,
};
builder.Bind<ViewManagerConfig>().ToInstance(viewManagerConfig).AsWeakBinding();
builder.Bind<IWindowManagerConfig>().ToInstance(this).AsWeakBinding();
builder.Bind<IViewManager>().To<ViewManager>().InSingletonScope().AsWeakBinding();
builder.Bind<IWindowManager>().To<WindowManager>().InSingletonScope().AsWeakBinding();

View File

@ -12,19 +12,13 @@ namespace Stylet
/// <summary>
/// Bootstrapper to be extended by applications which don't want to use StyletIoC as the IoC container.
/// </summary>
public abstract class BootstrapperBase : IBootstrapper, IViewManagerConfig, IWindowManagerConfig, IDisposable
public abstract class BootstrapperBase : IBootstrapper, IWindowManagerConfig, IDisposable
{
/// <summary>
/// Gets the current application
/// </summary>
public Application Application { get; private set; }
/// <summary>
/// Gets or sets assemblies which are used for IoC container auto-binding and searching for Views.
/// Set this in Configure() if you want to override it
/// </summary>
public IReadOnlyList<Assembly> Assemblies { get; protected set; }
/// <summary>
/// Gets the command line arguments that were passed to the application from either the command prompt or the desktop.
/// </summary>
@ -40,7 +34,6 @@ namespace Stylet
/// </summary>
protected BootstrapperBase()
{
this.Assemblies = new List<Assembly>() { typeof(BootstrapperBase).Assembly, this.GetType().Assembly };
}
/// <summary>

View File

@ -45,22 +45,19 @@ namespace Stylet
}
/// <summary>
/// Configuration passed to ViewManager (normally implemented by BootstrapperBase)
/// Configuration passed to ViewManager
/// </summary>
public interface IViewManagerConfig
public class ViewManagerConfig
{
/// <summary>
/// Gets the assemblies which are used for IoC container auto-binding and searching for Views.
/// Set this in Configure() if you want to override it
/// Gets and sets the assemblies which are used for IoC container auto-binding and searching for Views.
/// </summary>
IReadOnlyList<Assembly> Assemblies { get; }
public List<Assembly> ViewAssemblies { get; set; }
/// <summary>
/// Given a type, use the IoC container to fetch an instance of it
/// Gets and sets the delegate used to retrieve an instance of a view
/// </summary>
/// <param name="type">Type of instance to fetch</param>
/// <returns>Fetched instance</returns>
object GetInstance(Type type);
public Func<Type, object> ViewFactory { get; set; }
}
/// <summary>
@ -73,7 +70,7 @@ namespace Stylet
/// <summary>
/// Gets or sets the assemblies searched for View types
/// </summary>
protected IReadOnlyList<Assembly> Assemblies { get; set; }
protected List<Assembly> ViewAssemblies { get; set; }
/// <summary>
/// Gets or sets the factory used to create view instances from their type
@ -84,10 +81,10 @@ namespace Stylet
/// Initialises a new instance of the <see cref="ViewManager"/> class, with the given viewFactory
/// </summary>
/// <param name="config">Configuration to use</param>
public ViewManager(IViewManagerConfig config)
public ViewManager(ViewManagerConfig config)
{
this.Assemblies = config.Assemblies;
this.ViewFactory = config.GetInstance;
this.ViewAssemblies = config.ViewAssemblies;
this.ViewFactory = config.ViewFactory;
}
/// <summary>
@ -160,8 +157,7 @@ namespace Stylet
/// <returns>Type for that view name</returns>
protected virtual Type ViewTypeForViewName(string viewName)
{
// TODO: This might need some more thinking
return this.Assemblies.SelectMany(x => x.GetExportedTypes()).FirstOrDefault(x => x.FullName == viewName);
return this.ViewAssemblies.SelectMany(x => x.GetExportedTypes()).FirstOrDefault(x => x.FullName == viewName);
}
/// <summary>
@ -223,13 +219,23 @@ namespace Stylet
var view = (UIElement)this.ViewFactory(viewType);
this.InitializeView(view, viewType);
return view;
}
/// <summary>
/// Given a view, take steps to initialize it (for example calling InitializeComponent)
/// </summary>
/// <param name="view">View to initialize</param>
/// <param name="viewType">Type of view, passed for efficiency reasons</param>
public virtual void InitializeView(UIElement view, Type viewType)
{
// If it doesn't have a code-behind, this won't be called
// We have to use this reflection here, since the InitializeComponent is a method on the View, not on any of its base classes
var initializer = viewType.GetMethod("InitializeComponent", BindingFlags.Public | BindingFlags.Instance);
if (initializer != null)
initializer.Invoke(view, null);
return view;
}
/// <summary>

View File

@ -122,6 +122,10 @@ namespace Stylet
{
var vm = this.messageBoxViewModelFactory();
vm.Setup(messageBoxText, caption, buttons, icon, defaultResult, cancelResult, options, buttonLabels);
// Don't go through the IoC container to get the View. This means we can simplify it...
var messageBoxView = new MessageBoxView();
messageBoxView.InitializeComponent();
this.viewManager.BindViewToModel(messageBoxView, vm);
this.ShowDialog(vm);
return vm.ClickedButton;
}

View File

@ -24,11 +24,11 @@ namespace StyletUnitTests
private interface I1 { }
private abstract class AC1 { }
private class C1 { }
private Mock<IViewManagerConfig> viewManagerConfig;
private ViewManagerConfig viewManagerConfig;
private class AccessibleViewManager : ViewManager
{
public AccessibleViewManager(IViewManagerConfig config) : base(config) { }
public AccessibleViewManager(ViewManagerConfig config) : base(config) { }
public new UIElement CreateViewForModel(object model)
{
@ -61,7 +61,7 @@ namespace StyletUnitTests
public UIElement View;
public object RequestedModel;
public CreatingAndBindingViewManager(IViewManagerConfig config) : base(config) { }
public CreatingAndBindingViewManager(ViewManagerConfig config) : base(config) { }
public override UIElement CreateViewForModel(object model)
{
@ -80,7 +80,7 @@ namespace StyletUnitTests
private class LocatingViewManager : ViewManager
{
public LocatingViewManager(IViewManagerConfig config) : base(config) { }
public LocatingViewManager(ViewManagerConfig config) : base(config) { }
public Type LocatedViewType;
protected override Type LocateViewForModel(Type modelType)
@ -91,7 +91,7 @@ namespace StyletUnitTests
private class ResolvingViewManager : ViewManager
{
public ResolvingViewManager(IViewManagerConfig config) : base(config) { }
public ResolvingViewManager(ViewManagerConfig config) : base(config) { }
public Type ViewType;
protected override Type ViewTypeForViewName(string viewName)
@ -125,8 +125,8 @@ namespace StyletUnitTests
[SetUp]
public void SetUp()
{
this.viewManagerConfig = new Mock<IViewManagerConfig>();
this.viewManager = new AccessibleViewManager(this.viewManagerConfig.Object);
this.viewManagerConfig = new ViewManagerConfig();
this.viewManager = new AccessibleViewManager(this.viewManagerConfig);
}
[Test]
@ -163,7 +163,7 @@ namespace StyletUnitTests
var target = new ContentControl();
var model = new object();
var view = new UIElement();
var viewManager = new CreatingAndBindingViewManager(this.viewManagerConfig.Object);
var viewManager = new CreatingAndBindingViewManager(this.viewManagerConfig);
viewManager.View = view;
@ -181,7 +181,7 @@ namespace StyletUnitTests
var target = new ContentControl();
var model = new object();
var view = new Window();
var viewManager = new CreatingAndBindingViewManager(this.viewManagerConfig.Object);
var viewManager = new CreatingAndBindingViewManager(this.viewManagerConfig);
viewManager.View = view;
@ -191,11 +191,13 @@ namespace StyletUnitTests
[Test]
public void CreateViewForModelReturnsNullIfViewNotFound()
{
var config = new Mock<IViewManagerConfig>();
config.Setup(x => x.GetInstance(typeof(C1))).Returns(null);
config.SetupGet(x => x.Assemblies).Returns(new List<Assembly>());
var config = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { typeof(BootstrapperBase).Assembly, Assembly.GetExecutingAssembly() },
ViewFactory = type => null,
};
var viewManager = new AccessibleViewManager(config.Object);
var viewManager = new AccessibleViewManager(config);
Assert.IsNull(viewManager.ViewTypeForViewName("Test"));
}
@ -208,8 +210,8 @@ namespace StyletUnitTests
[Test]
public void LocateViewForModelThrowsIfTypeLocationDoesntWork()
{
var config = new Mock<IViewManagerConfig>();
var viewManager = new ResolvingViewManager(config.Object);
var config = new ViewManagerConfig();
var viewManager = new ResolvingViewManager(config);
viewManager.ViewType = null;
Assert.Throws<StyletViewLocationException>(() => viewManager.LocateViewForModel(typeof(C1)));
}
@ -217,9 +219,11 @@ namespace StyletUnitTests
[Test]
public void LocateViewForModelFindsViewForModel()
{
var config = new Mock<IViewManagerConfig>();
config.SetupGet(x => x.Assemblies).Returns(new List<Assembly>() { Assembly.GetExecutingAssembly() });
var viewManager = new AccessibleViewManager(config.Object);
var config = new ViewManagerConfig()
{
ViewAssemblies = new List<Assembly>() { Assembly.GetExecutingAssembly() }
};
var viewManager = new AccessibleViewManager(config);
var viewType = viewManager.LocateViewForModel(typeof(ViewManagerTestsViewModel));
Assert.AreEqual(typeof(ViewManagerTestsView), viewType);
}
@ -227,7 +231,7 @@ namespace StyletUnitTests
[Test]
public void CreateViewForModelIfNecessaryThrowsIfViewIsNotConcreteUIElement()
{
var viewManager = new LocatingViewManager(this.viewManagerConfig.Object);
var viewManager = new LocatingViewManager(this.viewManagerConfig);
viewManager.LocatedViewType = typeof(I1);
Assert.Throws<StyletViewLocationException>(() => viewManager.CreateAndBindViewForModelIfNecessary(new object()));
@ -243,9 +247,11 @@ namespace StyletUnitTests
public void CreateAndBindViewForModelIfNecessaryCallsFetchesViewAndCallsInitializeComponent()
{
var view = new TestView();
var config = new Mock<IViewManagerConfig>();
config.Setup(x => x.GetInstance(typeof(TestView))).Returns(view);
var viewManager = new LocatingViewManager(config.Object);
var config = new ViewManagerConfig()
{
ViewFactory = type => view
};
var viewManager = new LocatingViewManager(config);
viewManager.LocatedViewType = typeof(TestView);
var returnedView = viewManager.CreateAndBindViewForModelIfNecessary(new object());
@ -270,9 +276,11 @@ namespace StyletUnitTests
public void CreateViewForModelDoesNotComplainIfNoInitializeComponentMethod()
{
var view = new UIElement();
var config = new Mock<IViewManagerConfig>();
config.Setup(x => x.GetInstance(typeof(UIElement))).Returns(view);
var viewManager = new LocatingViewManager(config.Object);
var config = new ViewManagerConfig()
{
ViewFactory = type => view,
};
var viewManager = new LocatingViewManager(config);
viewManager.LocatedViewType = typeof(UIElement);
var returnedView = viewManager.CreateAndBindViewForModelIfNecessary(new object());
@ -284,7 +292,7 @@ namespace StyletUnitTests
public void BindViewToModelDoesNotSetActionTarget()
{
var view = new UIElement();
var viewManager = new AccessibleViewManager(this.viewManagerConfig.Object);
var viewManager = new AccessibleViewManager(this.viewManagerConfig);
viewManager.BindViewToModel(view, new object());
Assert.AreEqual(View.InitialActionTarget, View.GetActionTarget(view));
@ -295,7 +303,7 @@ namespace StyletUnitTests
{
var view = new FrameworkElement();
var model = new object();
var viewManager = new AccessibleViewManager(this.viewManagerConfig.Object);
var viewManager = new AccessibleViewManager(this.viewManagerConfig);
viewManager.BindViewToModel(view, model);
Assert.AreEqual(model, view.DataContext);
@ -306,7 +314,7 @@ namespace StyletUnitTests
{
var view = new UIElement();
var model = new Mock<IViewAware>();
var viewManager = new AccessibleViewManager(this.viewManagerConfig.Object);
var viewManager = new AccessibleViewManager(this.viewManagerConfig);
viewManager.BindViewToModel(view, model.Object);
model.Verify(x => x.AttachView(view));
@ -317,7 +325,7 @@ namespace StyletUnitTests
[Test]
public void ViewNameResolutionWorksAsExpected()
{
var viewManager = new AccessibleViewManager(this.viewManagerConfig.Object);
var viewManager = new AccessibleViewManager(this.viewManagerConfig);
Assert.AreEqual("Root.Test.ThingView", viewManager.ViewTypeNameForModelTypeName("Root.Test.ThingViewModel"));
Assert.AreEqual("Root.Views.ThingView", viewManager.ViewTypeNameForModelTypeName("Root.ViewModels.ThingViewModel"));