This is going to be a bit lengthy, so hold on tight.
I have a project that uses Caliburn.Micro to implement MVVM pattern. For IoC, I am using MEF. All the ViewModels are marked with attribute [Export(typeof(IScreen))] . IScreen is defined in Caliburn.Micro.
In my BootStrapper file I have set up MEF like so:
private CompositionContainer container;
protected override void Configure()
{
//Set up MEF
container = new CompositionContainer(new AggregateCatalog``AssemblySource.Instance.Select``(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
var batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(container);
container.Compose(batch);
//Other stuff
}
Now, I show the first ViewModel using
protected override void OnStartup(object sender, StartupEventArgs e) => DisplayRootViewFor<ViewModels.Main.MainViewModel>();
All that is fine and dandy. Now, the problem starts when I want to switch from one ViewModel to another.
An example of a ViewModel is :
[PartCreationPolicy(CreationPolicy.NonShared), Export(typeof(IScreen))]
sealed class SampleViewModel: ScreenVMwithUoW<IAddItemUoW>
{
[ImportingConstructor]
public SampleViewModel(IAddItemUoW uow, IEventAggregator eventAggregator) : base(uow, eventAggregator)
{
}
//bla bla bla
}
Let's say I want to open SampleViewModel and close my MainViewModel. How do I do it using MEF?
Here is what I am doing now:
[Export(typeof(IScreenFactory))]
class ScreenFactory : IScreenFactory
{
private Dictionary<Type, ExportFactory<IScreen>> _screenDictionary { get; set; }
private IEnumerable<ExportFactory<IScreen>> _screenfactoryCollection { get; set; }
private ExportLifetimeContext<IScreen> _currentScreenLifeTimeCtx { get; set; }
[ImportingConstructor]
public ScreenFactory([ImportMany(AllowRecomposition = true)] IEnumerable<ExportFactory<IScreen>> screenList)
{
_screenfactoryCollection = screenList;
_reportfactoryCollection = reportsList;
}
public IScreen GetScreen(Type t)
{
if (_screenDictionary == null)
{
PopulateScreenDictionary(_screenfactoryCollection);
}
_currentScreenLifeTimeCtx = _screenDictionary[t].CreateExport();
return _currentScreenLifeTimeCtx.Value;
}
private void PopulateScreenDictionary(IEnumerable<ExportFactory<IScreen>> screenfactories)
{
_screenDictionary = screenfactories.ToDictionary(c => c.CreateExport().Value.GetType(), c => c);
}
public void DisposeCurrentScreenContext()
{
_currentScreenLifeTimeCtx?.Dispose();
}
public void Dispose()
{
DisposeCurrentScreenContext();
}
}
Whenever I need a new instance of a ViewModel, I just do:
DeactivateItem(ActiveItem, true);
_vmFactory.DisposeCurrentScreenContext();
ActivateItem(_vmFactory.GetScreen(NextViewModelName));
This CANNOT be the normal way to do so. Also, this also adds a huge hit in performance, especially the startup time of the application, when the number of ViewModels increase to about 25-30.
I would also like to avoid the Service Locator Pattern, if possible. I am also open to using a proper IoC Container instead of MEF, but MEF allows me to add ViewModels with very little effort. Just adding the annotation [Export(typeof(IScreen))] does the job.
Related
When I am trying to publish an events from SimpleFreeDrawModifier class to FieldSettingViewModel class, Subscriber class not able to get subscribe the events or vice versa.
Publishing Events Code:
public SimpleFreeDrawModifier(RegionManager regionManager, EventAggregator eventAggragator) // Constructor
{
_regionManager = regionManager;
_eventAggragator = eventAggragator;
CorodinatesMapDictionary =new Dictionary<object, object>();
}
private void AppendPoint(Point mousePoint)
{
// On Mouse-down, append another point
if (_dataSeries != null)
{
CorodinatesMapDictionary.Clear();
_dataSeries.Append((double)XAxis.GetDataValue(mousePoint.X), (double)YAxis.GetDataValue(mousePoint.Y));
GlobalVar.XCoordinate=(double)_dataSeries.XValues[0];
GlobalVar.YCoordinate=(double)_dataSeries.YValues[0];
CorodinatesMapDictionary.Add("X", GlobalVar.XCoordinate);
CorodinatesMapDictionary.Add("Y", GlobalVar.YCoordinate);
var calc = Math.Abs(GlobalVar.XCoordinate) * 1.0913;
if (GlobalVar.YCoordinate < 0)
{
if (GlobalVar.YCoordinate >= -(calc))
{
_eventAggragator.GetEvent<MapCoOrdinateChangedEvents>().Publish();
}
}
else
{
if(Math.Abs(GlobalVar.XCoordinate) <=25 && GlobalVar.YCoordinate <=25)
{
_eventAggragator.GetEvent<MapCoOrdinateChangedEvents>().Publish();
}
}
}
}
Subscribing Events Code
public FieldSettingViewModel(RegionManager regionManager,IEventAggregator eventAggragator)
{
_regionManager = regionManager;
_eventAggragator = eventAggragator;
ClearCommand = new DelegateCommand(Clear);
ApplyFieldSettingCommand = new DelegateCommand(ApplyFieldSetting, canApplyFieldSetting);
ScanAreaDataSeries = new XyDataSeries<double, double>();
GetScanSeriesData();
_eventAggragator.GetEvent<MapCoOrdinateChangedEvents>().Subscribe(MapValue);
}
private void MapValue()
{
//throw new NotImplementedException();
}
Could you please provide any suggestions?
Correct me if I'm wrong but it looks like instantiation of SimpleFreeDrawModifier and FieldSettingViewModel is achieved through dependency injection.
Are you sure that an instance of FieldSettingViewModel has been created and the it's receiving the same instance of IEventAggregator?
This minimal example without DI works
using System.Drawing;
using Prism.Events;
var eventAggregator = new EventAggregator();
var pub = new Publisher(eventAggregator);
var sub = new Subscriber(eventAggregator);
pub.AppendPoint(new Point());
public class Publisher
{
private IEventAggregator _eventAggregator;
public Publisher(IEventAggregator eventAggregator) // Constructor
{
_eventAggregator = eventAggregator;
}
public void AppendPoint(Point mousePoint)
{
_eventAggregator.GetEvent<MapCoOrdinateChangedEvents>().Publish();
}
}
public class Subscriber
{
private IEventAggregator _eventAggregator;
public Subscriber(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
_eventAggregator.GetEvent<MapCoOrdinateChangedEvents>().Subscribe(MapValue);
}
private void MapValue()
{
Console.WriteLine("Map value called");
}
}
internal class MapCoOrdinateChangedEvents : PubSubEvent
{
}
Check the DI lifetime of your EventAggregator is Singleton and not Transient, also try putting a breakpoint in the constructor of FieldSettingViewModel to ensure it is run.
If it's not being run and you're registering it for DI like services.AddSingleton<FieldSettingViewModel>(), try instead using services.AddSingleton<FieldSettingViewModel>(new FieldSettingViewModel()) to ensure there is one instantiated.
The constructor of SimpleFreeDrawModifier use EventAggregator instead of the interface IEventAggregator therefore as precised by hwoodiwiss they are not using the same instance even if it has been registered as singleton.
It already happenned to me and it took me quite some time to see the missing I
I'm stuck!
I'm using Caliburn.micro to take away some of the pain implementing MVVM in a WPF application.
Currently I only have a single View / ViewModel, but in the future there could be several ViewModels. The current ViewModel is using a Repository to populate a list of objects:
public class ShellViewModel : Screen
{
private IMyObjectRepository<IMyObject> _myObjectsRepo = null;
private BindableCollection<MyObject> _myObjects;
private string _connString;
/// <summary>
/// constructor
/// </summary>
public ShellViewModel()
{
//call the method which sets up the repository
GetMyObjectsRepository();
//following three lines cast the list from type IReport to type Report
var IMyObjects= _myObjectsRepo.GetAllIMyObjects();
var myObjects = IMyObjects.OfType<MyObject>().ToList();
MyObjects = new BindableCollection<MyObject>(myObjects );
}
private void GetMyObjectsRepository()
{
_connString = ConfigurationManager.ConnectionStrings["xxx"].ConnectionString;
_myObjectRepo = MyObjectRepositoryFactory.InstantiateRepo(_connString);
}
The above smells like a future problem - if I create a different ViewModel which has its own attribute of BindableCollection<MyObject> _myObjects; then the two collections, of the same objects, could quickly have different states i.e. ObjectX in the first ViewModel might change its name attribute, but ObjectX still has its original name in the second ViewModel.
I was thinking I could inject this list of <MyObject> into the ViewModel when it is constructed - should I do this in Bootstrapper.cs? (I'd rather avoid full blown DI as this is a small project)
Currently Bootstrapper.cs looks like the following - how do I move some of the logic in the above code snippet into here? Does it go in the OnStartUp event method? If so then how?
using Caliburn.Micro;
using Prototype_WPF.ViewModels;
using System.Windows;
namespace Prototype_WPF
{
public class Bootstrapper: BootstrapperBase
{
public Bootstrapper()
{
Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
}
}
You could use the Handler method to register factory methods for your view models in the bootstrapper:
public class Bootstrapper : BootstrapperBase
{
private readonly BindableCollection<MyObject> _myObjects = new BindableCollection<MyObject>();
private SimpleContainer container;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
container = new SimpleContainer();
container.Singleton<IWindowManager, WindowManager>();
container.Handler<ShellViewModel>(_ => new ShellViewModel(_myObjects));
container.Handler<SomeOtherViewModel>(_ => new SomeOtherViewModel(_myObjects));
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor(typeof(ShellViewModel));
}
protected override object GetInstance(Type service, string key)
{
return container.GetInstance(service, key);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
container.BuildUp(instance);
}
}
You can create a separate component for retrieving your objects, something like IMyObjectService and implement it.
public interface IMyObjectService
{
IList<MyObject> GetMyObjects();
}
Than create an overload of Configure method in Bootstrapper and register an implementation, something like that:
protected override void Configure()
{
container = new SimpleContainer();
container.Singleton<IMyObjectService, MyObjectService>();
//other registrations
container.PerRequest<ShellViewModel>();
}
And finally use a constructor injection for any ViewModel to inject and use this service. The Caliburn.Micro documentation already has some examples
You can 'Register' the service and 'Register' every class like below
protected override void Configure()
{
container = new SimpleContainer();
container.Instance(container);
container
.Singleton<IMyObjectService, MyObjectService>();
//Register all ViewModel classes
GetType().Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModleType => container.RegisterPerRequest(
viewModleType, viewModleType.ToString(), viewModleType));
}
I have the following situation.
SomeClass has a dependency on IDiagram and Diagram implements that interface. The lifetime of SomeClass is the lifetime of the Application, however the lifetime a Diagram is shorter. Say it could change when a certain button is pressed.
Since I could not find anything satisfying on this problem I came up with the pattern depicted in the Diagram below.
The Observer of the Diagram would be aware that the Diagram can change and set the correct instance when it changes.
The Observer would implement the IDiagram interface by delegating the methods of the current Diagram instance.
SomeFactory would create new Diagrams and RaiseChanged.
SomeClass would not be aware of any of this.
Is enforcing this pattern a good idea, which downsides are there? Is there a better solution to this problem?
Example code with IDependency instead of IDiagram below:
private static void Main(string[] args)
{
var transientDependency = new TransientDependency();
var dependencyObserver = new DependecyObserver(transientDependency);
var dependencyFactory = new Factory(transientDependency);
var someClass = new SomeClass(dependencyObserver);
var someOtherClass = new SomeClass(dependencyObserver);
// Note that someClass can only be used after the dependency has been created, because the Changed event has to be invoked
dependencyFactory.CreateDependency();
}
public class DependecyObserver : IDependency
{
public DependecyObserver(TransientDependency transient)
{
transient.Changed += (s, dependency) => Dependency = dependency;
}
private Dependency Dependency { get; set; }
public void SomeMethod()
{
Dependency.SomeMethod();
}
}
public class Factory
{
private TransientDependency TransientDependency { get; }
public Factory(TransientDependency transientDependency)
{
TransientDependency = transientDependency;
}
public void CreateDependency()
{
TransientDependency.RaiseChanged(new Dependency());
}
}
public class SomeClass
{
public SomeClass(IDependency dependency)
{
dependency.SomeMethod();
}
}
public class TransientDependency : TransientInstance<Dependency> { }
public abstract class TransientInstance<T>
{
public EventHandler<T> Changed;
public void RaiseChanged(T instance)
{
Changed?.Invoke(this, instance);
}
}
public class Dependency : IDependency
{
public void SomeMethod()
{
throw new NotImplementedException();
}
}
public interface IDependency
{
void SomeMethod();
}
i wrote a little example to learn IoC and DI on my own.
I have one simple question:
How would you instantiate the unskilled worker in my example /
How can I swich between the following 2 inject candidates?:
kernal.Bind<IRepair>().To<Employee>();
kernal.Bind<IRepair>().To<UnskilledWorker>()
I'm a little bit confused at the moment...
class Program
{
static void Main(string[] args)
{
IWorkShop instance = GetWorkShop();
instance.StartToRepair();
Console.ReadLine();
}
private static IWorkShop GetWorkShop()
{
Ninject.IKernel kernal = new StandardKernel();
kernal.Bind<IWorkShop>().To<WorkShop>();
kernal.Bind<IRepair>().To<Employee>();
var instance = kernal.Get<IWorkShop>();
return instance;
}
}
public class WorkShop : IWorkShop
{
private IRepair _repair;
public WorkShop(IRepair repair)
{
_repair = repair;
}
public void StartToRepair()
{
_repair.RepairItNow();
}
}
interface IWorkShop
{
void StartToRepair();
}
public class Employee : IRepair
{
public void RepairItNow()
{
Console.WriteLine("Employee starts working.");
}
}
public class UnskilledWorker : IRepair
{
public void RepairItNow()
{
Console.WriteLine("Unskilled worker starts working.");
}
}
public interface IRepair
{
void RepairItNow();
}
}
If you know at compile time then you can use Ninject's contextual bindings: https://github.com/ninject/ninject/wiki/Contextual-Binding.
IKernel kernal = new StandardKernel();
kernal.Bind<IWorkShop>().To<WorkShop>();
kernal.Bind<IRepair>().To<Employee>();
kernal.Bind<IRepair>().To<UnskilledWorker>().WhenInjectedInto(typeof(IWorkShop));
var instance = kernal.Get<IWorkShop>();
return instance;
If you need to decide at runtime which dependency to instantiate you are going to have to use a factory pattern.
I have created a WinForms MVC application using Dependency Injection (DI) and Ninject as the DI Container. The basic architecture is as follows
Program.cs (the main entry point of the WinForms application):
static class Program
{
[STAThread]
static void Main()
{
...
CompositionRoot.Initialize(new DependencyModule());
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(CompositionRoot.Resolve<ApplicationShellView>());
}
}
DependencyModule.cs
public class DependencyModule : NinjectModule
{
public override void Load()
{
Bind<IApplicationShellView>().To<ApplicationShellView>();
Bind<IDocumentController>().To<SpreadsheetController>();
Bind<ISpreadsheetView>().To<SpreadsheetView>();
}
}
CompositionRoot.cs
public class CompositionRoot
{
private static IKernel ninjectKernel;
public static void Initialize(INinjectModule module)
{
ninjectKernel = new StandardKernel(module);
}
public static T Resolve<T>()
{
return ninjectKernel.Get<T>();
}
public static IEnumerable<T> ResolveAll<T>()
{
return ninjectKernel.GetAll<T>();
}
}
ApplicationShellView.cs (the main form of the application)
public partial class ApplicationShellView : C1RibbonForm, IApplicationShellView
{
private ApplicationShellController controller;
public ApplicationShellView()
{
this.controller = new ApplicationShellController(this);
InitializeComponent();
}
public void InitializeView()
{
dockPanel.Extender.FloatWindowFactory = new CustomFloatWindowFactory();
dockPanel.Theme = vS2012LightTheme;
}
private void ribbonButtonTest_Click(object sender, EventArgs e)
{
controller.OpenNewSpreadsheet();
}
public DockPanel DockPanel
{
get { return dockPanel; }
}
}
where
public interface IApplicationShellView
{
void InitializeView();
DockPanel DockPanel { get; }
}
ApplicationShellController.cs
public class ApplicationShellController
{
private IApplicationShellView shellView;
public ApplicationShellController(IApplicationShellView view)
{
this.shellView = view;
}
public void OpenNewSpreadsheet(DockState dockState = DockState.Document)
{
SpreadsheetController controller = (SpreadsheetController)GetDocumentController("new.xlsx");
SpreadsheetView view = (SpreadsheetView)controller.New("new.xlsx");
view.Show(shellView.DockPanel, dockState);
}
private IDocumentController GetDocumentController(string path)
{
return CompositionRoot.ResolveAll<IDocumentController>()
.SingleOrDefault(provider => provider.Handles(path));
}
public IApplicationShellView ShellView { get { return shellView; } }
}
SpreadsheetController.cs
public class SpreadsheetController : IDocumentController
{
private ISpreadsheetView view;
public SpreadsheetController(ISpreadsheetView view)
{
this.view = view;
this.view.SetController(this);
}
public bool Handles(string path)
{
string extension = Path.GetExtension(path);
if (!String.IsNullOrEmpty(extension))
{
if (FileTypes.Any(ft => ft.FileExtension.CompareNoCase(extension)))
return true;
}
return false;
}
public void SetViewActive(bool isActive)
{
((SpreadsheetView)view).ShowIcon = isActive;
}
public IDocumentView New(string fileName)
{
// Opens a new file correctly.
}
public IDocumentView Open(string path)
{
// Opens an Excel file correctly.
}
public IEnumerable<DocumentFileType> FileTypes
{
get
{
return new List<DocumentFileType>()
{
new DocumentFileType("CSV", ".csv" ),
new DocumentFileType("Excel", ".xls"),
new DocumentFileType("Excel10", ".xlsx")
};
}
}
}
where the implemented interface is
public interface IDocumentController
{
bool Handles(string path);
void SetViewActive(bool isActive);
IDocumentView New(string fileName);
IDocumentView Open(string path);
IEnumerable<DocumentFileType> FileTypes { get; }
}
Now the view ascociated with this controller is
public partial class SpreadsheetView : DockContent, ISpreadsheetView
{
private IDocumentController controller;
public SpreadsheetView()
{
InitializeComponent();
}
private void SpreadsheetView_Activated(object sender, EventArgs e)
{
controller.SetViewActive(true);
}
private void SpreadsheetView_Deactivate(object sender, EventArgs e)
{
controller.SetViewActive(false);
}
public void SetController(IDocumentController controller)
{
this.controller = controller;
Log.Trace("SpreadsheetView.SetController(): Controller set successfully");
}
public string DisplayName
{
get { return Text; }
set { Text = value; }
}
public WorkbookView WorkbookView
{
get { return workbookView; }
set { workbookView = value; }
}
...
}
Finally the view interfaces are
public interface ISpreadsheetView : IDocumentView
{
WorkbookView WorkbookView { get; set; }
}
and
public interface IDocumentView
{
void SetController(IDocumentController controller);
string DisplayName { get; set; }
bool StatusBarVisible { get; set; }
}
Now for my questions. In Seemann's book "Dependency Injection in .NET" he talks about the "Three Calls Pattern" and this is what I have attempted to implement in the above. The code works, the shell view displays and via the MVC pattern my controllers correctly open views etc. However, I am confused as the above definately has the flavour of the "Service Locator Anti-Pattern". In chapter 3 of Seemann's book he states
The COMPOSITION ROOT pattern describes where you should use a DI CONTAINER. However,
it doesn’t state how to use it. The REGISTER RESOLVE RELEASE pattern addresses
this question [...] A DI CONTAINER should be used in three successive
phases called Register, Resolve, and Release.
In its pure form, the REGISTER RESOLVE RELEASE pattern states that you should only
make a single method call in each phase. Krzysztof Kozimic calls this the Three Calls Pattern.
Configuring a DI CONTAINER in a single method call requires more explanation. The
reason that registration of components should happen in a single method call is
because you should regard configuration of a DI CONTAINER as a single, atomic action.
Once configuration is completed, the container should be regarded as read-only.
This sounds like the dredded "Service locator", why is this not deemed service location?
In order to adjust my code to instead use Contstructor Injection, I changed my entry code to
[STAThread]
static void Main()
{
var kernel = new StandardKernel();
kernel.Bind(t => t.FromThisAssembly()
.SelectAllClasses()
.BindAllInterfaces());
FileLogHandler fileLogHandler = new FileLogHandler(Utils.GetLogFilePath());
Log.LogHandler = fileLogHandler;
Log.Trace("Program.Main(): Logging initialized");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(kernel.Get<ApplicationShellView>());
}
using Ninject.Extensions.Conventions, I then changed ApplicationShellController in order to correct my code to inject the IDocumentControllers via ctor injection:
public class ApplicationShellController
{
private IApplicationShellView shellView;
private IEnumerable<IDocumentController> controllers;
public ApplicationShellController(IApplicationShellView shellView, IEnumerable<IDocumentController> controllers)
{
this.shellView = shellView;
this.controllers = controllers;
Log.Trace("ApplicationShellController.Ctor(): Shell initialized successfully");
}
...
}
where
public class SpreadsheetController : IDocumentController
{
private ISpreadsheetView view;
public SpreadsheetController(ISpreadsheetView view)
{
this.view = view;
this.view.SetController(this);
}
...
}
but this leads to a circular dependency, how do I handle this?
Question Summary:
Why is my initial use of Ninject using "Thee Calls Pattern" and CompositionRoot.Resolve<T>() bad or different to the Service Locator Anti-Pattern?
How can I resolve the circular dependency issue above if I want to switch to pure ctor injection?
Thanks very much for your time.
At some point in the process, you have to use service location. However, the difference between DI and SL is that in SL, you are resolving your services at the point they are requested, whereas in DI you resolve them in some kind of factory (such as a controller factory) and then construct your objects and pass the reference in.
You should create some kind of infrastructure that dispatches your commands and uses a factory of some kind to locate the dependencies used by the created objects.
In this way, the rest of your code doesn't have dependency resolution, and you are following a DI pattern except at the construction point.