Added option to create service scopes per window or dialog by overriding the WindowManager.CreateScope() method
This commit is contained in:
parent
f6457b1909
commit
d057f70449
45
Source/IWindowScope.cs
Normal file
45
Source/IWindowScope.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nuclex.Windows.Forms {
|
||||||
|
|
||||||
|
/// <summary>Constructs views and view model in a scope</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// By default the <see cref="WindowManager" /> uses its own
|
||||||
|
/// <see cref="WindowManager.CreateInstance" /> method to construct views
|
||||||
|
/// and view models via <see cref="Activator.CreateInstance(Type)" />,
|
||||||
|
/// which is enough to create forms (which the Windows Forms designer already
|
||||||
|
/// requires to have parameterless constructors) and view models, so long as
|
||||||
|
/// they also have parameterless constructors.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// To support dependency injection via constructor parameters, you can
|
||||||
|
/// inherit from the <see cref="WindowManager" /> and provide your own override
|
||||||
|
/// of <see cref="WindowManager.CreateInstance" /> that constructs the required
|
||||||
|
/// instance via your dependency injector. This is decent until you have multiple
|
||||||
|
/// view models all accessing the same resource (i.e. a database) via threads.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// In this final case, "scopes" have become a common solution. Each
|
||||||
|
/// scope has access to singleton services (these exist for the lifetime of
|
||||||
|
/// the entire application), but there are also scoped services which will have
|
||||||
|
/// new instances constructed within each scope. By implementing the
|
||||||
|
/// <see cref="WindowManager.CreateWindowScope" /> method, you can make
|
||||||
|
/// the window manager set up an implicit scope per window or dialog.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public interface IWindowScope : IDisposable {
|
||||||
|
|
||||||
|
/// <summary>Creates an instance of the specified type in the scope</summary>
|
||||||
|
/// <param name="type">Type an instance will be created of</param>
|
||||||
|
/// <returns>The created instance</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Use this to wire up your dependency injection container. By default,
|
||||||
|
/// the Activator class will be used to create instances which only works
|
||||||
|
/// if all of your view models are concrete classes.
|
||||||
|
/// </remarks>
|
||||||
|
object CreateInstance(Type type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Nuclex.Windows.Forms
|
@ -39,6 +39,36 @@ namespace Nuclex.Windows.Forms {
|
|||||||
#endif
|
#endif
|
||||||
public class WindowManager : Observable, IWindowManager {
|
public class WindowManager : Observable, IWindowManager {
|
||||||
|
|
||||||
|
#region class WindowManagerScope
|
||||||
|
|
||||||
|
/// <summary>Global scope that uses the WindowManager's CreateInstance()</summary>
|
||||||
|
public class WindowManagerScope : IWindowScope {
|
||||||
|
|
||||||
|
/// <summary>Initializes a new global window scope</summary>
|
||||||
|
/// <param name="windowManager">
|
||||||
|
/// Window manager whose CreateInstance() method will be used
|
||||||
|
/// </param>
|
||||||
|
public WindowManagerScope(WindowManager windowManager) {
|
||||||
|
this.windowManager = windowManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates an instance of the specified type in the scope</summary>
|
||||||
|
/// <param name="type">Type an instance will be created of</param>
|
||||||
|
/// <returns>The created instance</returns>
|
||||||
|
object IWindowScope.CreateInstance(Type type) {
|
||||||
|
return this.windowManager.CreateInstance(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Does nothing because this is the global fallback scope</summary>
|
||||||
|
void IDisposable.Dispose() {}
|
||||||
|
|
||||||
|
/// <summary>WindowManager whose CreateInstance() method will be wrapped</summary>
|
||||||
|
private WindowManager windowManager;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion // class WindwoManagerScope
|
||||||
|
|
||||||
#region class CancellableDisposer
|
#region class CancellableDisposer
|
||||||
|
|
||||||
/// <summary>Disposes an object that potentially implements IDisposable</summary>
|
/// <summary>Disposes an object that potentially implements IDisposable</summary>
|
||||||
@ -91,6 +121,7 @@ namespace Nuclex.Windows.Forms {
|
|||||||
|
|
||||||
this.rootWindowActivatedDelegate = rootWindowActivated;
|
this.rootWindowActivatedDelegate = rootWindowActivated;
|
||||||
this.rootWindowClosedDelegate = rootWindowClosed;
|
this.rootWindowClosedDelegate = rootWindowClosed;
|
||||||
|
this.windowManagerAsScope = new WindowManagerScope(this);
|
||||||
this.viewTypesForViewModels = new ConcurrentDictionary<Type, Type>();
|
this.viewTypesForViewModels = new ConcurrentDictionary<Type, Type>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,8 +224,13 @@ namespace Nuclex.Windows.Forms {
|
|||||||
public virtual Control CreateView<TViewModel>(
|
public virtual Control CreateView<TViewModel>(
|
||||||
TViewModel viewModel = null
|
TViewModel viewModel = null
|
||||||
) where TViewModel : class {
|
) where TViewModel : class {
|
||||||
|
Control viewControl;
|
||||||
|
{
|
||||||
Type viewType = LocateViewForViewModel(typeof(TViewModel));
|
Type viewType = LocateViewForViewModel(typeof(TViewModel));
|
||||||
Control viewControl = (Control)CreateInstance(viewType);
|
|
||||||
|
IWindowScope scope = CreateWindowScope();
|
||||||
|
using(var scopeDisposer = new CancellableDisposer(scope)) {
|
||||||
|
viewControl = (Control)scope.CreateInstance(viewType);
|
||||||
using(var viewDisposer = new CancellableDisposer(viewControl)) {
|
using(var viewDisposer = new CancellableDisposer(viewControl)) {
|
||||||
|
|
||||||
// Create a view model if none was provided, and in either case assign
|
// Create a view model if none was provided, and in either case assign
|
||||||
@ -203,16 +239,16 @@ namespace Nuclex.Windows.Forms {
|
|||||||
IView viewControlAsView = viewControl as IView;
|
IView viewControlAsView = viewControl as IView;
|
||||||
if(viewModel == null) { // No view model provided, create one
|
if(viewModel == null) { // No view model provided, create one
|
||||||
if(viewControlAsView == null) { // View doesn't implement IView
|
if(viewControlAsView == null) { // View doesn't implement IView
|
||||||
viewModel = (TViewModel)CreateInstance(typeof(TViewModel));
|
viewModel = (TViewModel)scope.CreateInstance(typeof(TViewModel));
|
||||||
viewModelDisposer.Set(viewModel);
|
viewModelDisposer.Set(viewModel);
|
||||||
} else if(viewControlAsView.DataContext == null) { // View has no view model
|
} else if(viewControlAsView.DataContext == null) { // View has no view model
|
||||||
viewModel = (TViewModel)CreateInstance(typeof(TViewModel));
|
viewModel = (TViewModel)scope.CreateInstance(typeof(TViewModel));
|
||||||
viewModelDisposer.Set(viewModel);
|
viewModelDisposer.Set(viewModel);
|
||||||
viewControlAsView.DataContext = viewModel;
|
viewControlAsView.DataContext = viewModel;
|
||||||
} else { // There's an existing view model
|
} else { // There's an existing view model
|
||||||
viewModel = viewControlAsView.DataContext as TViewModel;
|
viewModel = viewControlAsView.DataContext as TViewModel;
|
||||||
if(viewModel == null) { // The existing view model is another type
|
if(viewModel == null) { // The existing view model is another type
|
||||||
viewModel = (TViewModel)CreateInstance(typeof(TViewModel));
|
viewModel = (TViewModel)scope.CreateInstance(typeof(TViewModel));
|
||||||
viewModelDisposer.Set(viewModel);
|
viewModelDisposer.Set(viewModel);
|
||||||
viewControlAsView.DataContext = viewModel;
|
viewControlAsView.DataContext = viewModel;
|
||||||
}
|
}
|
||||||
@ -230,9 +266,14 @@ namespace Nuclex.Windows.Forms {
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewDisposer.Dismiss(); // Everything went well, we keep the view
|
viewDisposer.Dismiss(); // Everything went well, we keep the view
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scopeDisposer.Dismiss(); // Everything went well, we keep the scope
|
||||||
|
}
|
||||||
|
|
||||||
|
setupScopeDisposal(viewControl, scope);
|
||||||
|
} // beauty scope
|
||||||
|
|
||||||
return viewControl;
|
return viewControl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,8 +363,18 @@ namespace Nuclex.Windows.Forms {
|
|||||||
return Activator.CreateInstance(type);
|
return Activator.CreateInstance(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual object CreateServiceScope() {
|
/// <summary>Creates an instance of the specified type in a new scope</summary>
|
||||||
return null;
|
/// <param name="type">Type an instance will be created of</param>
|
||||||
|
/// <returns>The created instance and the scope in which it lives</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is identical to <see cref="CreateInstance" /> but, if used together
|
||||||
|
/// with a dependency injector, should also create a service scope. This way,
|
||||||
|
/// an implicit service scope will cover the lifetime of a view model and
|
||||||
|
/// any non-singleton services will use new instances, avoiding, for example,
|
||||||
|
/// that multiple dialogs access the same database connection simultaneously.
|
||||||
|
/// </remarks>
|
||||||
|
protected virtual IWindowScope CreateWindowScope() {
|
||||||
|
return this.windowManagerAsScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Called when one of the application's root windows is closed</summary>
|
/// <summary>Called when one of the application's root windows is closed</summary>
|
||||||
@ -338,6 +389,7 @@ namespace Nuclex.Windows.Forms {
|
|||||||
ActiveWindow = null;
|
ActiveWindow = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Application.Run() already does this and it's the user's responsibility anyways
|
||||||
//disposeIfDisposable(closedWindow);
|
//disposeIfDisposable(closedWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -392,7 +444,7 @@ namespace Nuclex.Windows.Forms {
|
|||||||
/// <param name="control">
|
/// <param name="control">
|
||||||
/// Control whose view model will be disposed when it is itself disposed
|
/// Control whose view model will be disposed when it is itself disposed
|
||||||
/// </param>
|
/// </param>
|
||||||
private void setupViewModelDisposal(Control control) {
|
private static void setupViewModelDisposal(Control control) {
|
||||||
IView controlAsView = control as IView;
|
IView controlAsView = control as IView;
|
||||||
if(controlAsView != null) {
|
if(controlAsView != null) {
|
||||||
IDisposable disposableViewModel = controlAsView.DataContext as IDisposable;
|
IDisposable disposableViewModel = controlAsView.DataContext as IDisposable;
|
||||||
@ -408,6 +460,21 @@ namespace Nuclex.Windows.Forms {
|
|||||||
//window.SetValue(DisposeViewModelOnCloseProperty, true);
|
//window.SetValue(DisposeViewModelOnCloseProperty, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Attaches a scope disposer to a control</summary>
|
||||||
|
/// <param name="control">
|
||||||
|
/// Control that will dispose a scope when it is itself disposed
|
||||||
|
/// </param>
|
||||||
|
private void setupScopeDisposal(Control control, IWindowScope scope) {
|
||||||
|
if(!ReferenceEquals(scope, this.windowManagerAsScope)) {
|
||||||
|
IDisposable disposableScope = scope as IDisposable;
|
||||||
|
if(disposableScope != null) {
|
||||||
|
control.Disposed += delegate(object sender, EventArgs arguments) {
|
||||||
|
disposableScope.Dispose();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Filters a list of types to contain only those in a specific namespace</summary>
|
/// <summary>Filters a list of types to contain only those in a specific namespace</summary>
|
||||||
/// <param name="exportedTypes">List of exported types that will be filtered</param>
|
/// <param name="exportedTypes">List of exported types that will be filtered</param>
|
||||||
/// <param name="filteredNamespace">
|
/// <param name="filteredNamespace">
|
||||||
@ -432,6 +499,8 @@ namespace Nuclex.Windows.Forms {
|
|||||||
private EventHandler rootWindowActivatedDelegate;
|
private EventHandler rootWindowActivatedDelegate;
|
||||||
/// <summary>Invoked when a root window has been closed</summary>
|
/// <summary>Invoked when a root window has been closed</summary>
|
||||||
private EventHandler rootWindowClosedDelegate;
|
private EventHandler rootWindowClosedDelegate;
|
||||||
|
/// <summary>Scope that uses the WindowManager's global CreateInstance() method</summary>
|
||||||
|
private WindowManagerScope windowManagerAsScope;
|
||||||
/// <summary>View model binder that will be used on all created views</summary>
|
/// <summary>View model binder that will be used on all created views</summary>
|
||||||
private IAutoBinder autoBinder;
|
private IAutoBinder autoBinder;
|
||||||
/// <summary>Caches the view types to use for a view model</summary>
|
/// <summary>Caches the view types to use for a view model</summary>
|
||||||
|
Loading…
Reference in New Issue
Block a user