How to utilise Event aggregator in MEF? - c#

I am running the latest PRISM 4.2. Unfortunately the Event Aggregator tutorial in the documentation is driven via Unity instead of MEF. And I can't get it running under MEF.
App.xaml.cs
public partial class App : Application
{
[Import]
public IEventAggregator eventAggregator;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
}
}
Bootstrapper.cs
public class Bootstrapper : MefBootstrapper
{
protected override DependencyObject CreateShell()
{
return new MainWindow();
}
protected override void InitializeShell()
{
base.InitializeShell();
App.Current.MainWindow = (Window)Shell;
App.Current.MainWindow.Show();
}
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));
}
protected override IModuleCatalog CreateModuleCatalog()
{
ModuleCatalog moduleCatalog = new ModuleCatalog();
return moduleCatalog;
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window, IView
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
MainViewModel.cs:
[ModuleExport(typeof(MainViewModel))]
public class MainViewModel : BindableBase, IServiceCallback
{
[Import]
public IEventAggregator eventAggregator;
[ImportingConstructor]
public MainViewModel()
{
eventAggregator.GetEvent<AppExitEvent>().Publish("");
}
}
Despite the [import] the event aggregator is always null both in App.xaml.cs and in MainViewModel. Why is that?
The second question is do I have to export my Viewmodels as a module (as I did above) to use an even aggregator inside them?
UPDATE:
Proof that latest version of PRISM doesn't support ComposeExportedValue anymore.
'System.ComponentModel.Composition.Hosting.CompositionContainer' does
not contain a definition for 'ComposeExportedValue' and no extension
method ...

The solution to this would be what SchubertJ replied on your same question at CodePlex:
How to utilise Event aggregator in MEF?
As a deeper analyisis, the Import attribute on Properties would not be resolved until the constructor finishes. That is why you would need to inject the EventAggregator dependency through the constructor as a parameter if this dependency would be used on the constructor implementation.
Therefore, if you would like to use the EventAggregator dependency on the ViewModel constructor, you should use [ImportConstructor] attribute instead, and ask the container for the EventAggregator instance by retreiving it as a parameter:
public class MainViewModel
{
private readonly IEventAggregator eventAggregator;
[ImportingConstructor]
public MainViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
this.eventAggregator.GetEvent<MyEvent>().Publish("");
}
}
You may find more related information regarding both import alternatives in the following post:
MEF - [Import] vs [ImportingConstructor]
I hope this helped you, Regards.

In your bootstrapper class have this method :
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.ComposeExportedValue(new EventAggregator());
}
You should look at this article as it is answer both your first and second question in better details.
http://www.gonetdotnet.info/posts/wpf-articles/wpf-module-communication
Update:
If you create a class as below it will match your export with your import.
public class EventAggProvider
{
[Export(typeof(IEventAggregator))]
public IEventAggregator eventAggregator { get { return new EventAggregator(); } }
}

It is a little late, but there is also a solution for the case in which you want the EventAggregator gets injected in property. Implement 'IPartImportsSatisfiedNotification' and use the eventaggregator in 'OnImportsSatisfied' method.
public class MainViewModel : BindableBase, IPartImportsSatisfiedNotification
{
[Import]
public IEventAggregator eventAggregator;
public void OnImportsSatisfied()
{
eventAggregator.GetEvent<AppExitEvent>().Publish("");
}
}

EventAggregator does not depend on MEF or Unity it's design pattern coined by Martin Fowler and it's based on publisher subscriber scenario
try to follow this steps
//this should be in your infrastructure layer
public static class EventAggregatorHelper
{
private static IEventAggregator _Current = new EventAggregator();
public static IEventAggregator Current
{
get
{
return _Current;
}
}
}
The HandleAppExitEvent is a class declared as shown below:
public class AppExitEvent : CompositePresentationEvent<String>
{
}
and the subscriber would be something like this:
in your MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
//In this case the HandleAppExitEvent and the subscriber that deals with this event.
EventAggregatorHelper.Current.GetEvent<AppExitEvent>(). Subscribe(HandleAppExitEvent);
}
//note that this method should be public
public void HandleAppExitEvent(string mess)
{
if (!String.IsNullOrEmpty(mess))
{
//Do whatever you need to do here.
}
}
}

Related

Why my Subscribe method is not called when using Prism EventAggregator?

I am learning Prism. Few hours already I am facing a problem, when subscribing to the event, the subscription method is not called. I am using Prism and Autofac.
In the simplified example below, in MainViewModel Publish("dupa"); event is called in the ctor. And on button click UpdateWindow is opened. In backend of the window is created instance of UpdateViewModel.
Inside of update VM ctor is ran, but after Subscribe(UpdateName); the UpdateName is not executed, because of some reason that I do not understand.
Complete code:
public class MainViewModel : ViewModelBase
{
private IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator; //Prism
_eventAggregator.GetEvent<UpdateNameEvent>().Publish("dupa");
OnOpenCommand = new DelegateCommand(OnOpenWin);
}
public void OnOpenWin(object obj)
{
UpdateWindow win = new UpdateWindow();
win.Show();
}
public ICommand OnOpenCommand { get; private set; }
}
public class UpdateViewModel : ViewModelBase
{
private IEventAggregator _eventAggregator;
public UpdateViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator; //Prism
_eventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
}
private void UpdateName(string name)
{
this.Name = name; //is not called at all
}
private string _name;
public string Name
{
get
{
return _name;
}
set
{
_name = value;
OnPropertyChanged();
}
}
}
public partial class UpdateWindow : Window
{
public UpdateWindow()
{
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
UpdateViewModel vm = container.Resolve<UpdateViewModel>();
InitializeComponent();
DataContext = vm;
}
}
UPDATE
After investigating, I notied, that when subscribing to the events like this, it works fine:
Utility.EventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
When subscribing with used injected eventAggregator, it does not work:
_eventAggregator.GetEvent<UpdateNameEvent>().Subscribe(UpdateName);
And EventAggregator is registered by Autofac as follows:
builder.RegisterType<EventAggregator>()
.As<IEventAggregator>().SingleInstance();
I do not understand why this dependency does not work?
I do not understand why this dependency does not work?
Because you create a new EventAggregator for the UpdateViewModel.
var bootStrapper = new BootStrapper();
var container = bootStrapper.BootStrap();
UpdateViewModel vm = container.Resolve<UpdateViewModel>();
This looks as if a new container is created for the UpdateWindow, and the new container will have a new - that is, a different - EventAggregator. Those two will not send events to each other, of course.
So the solution is to use one single container to resolve all your stuff. This is what happens when you use the static Utility. You should avoid using a service-locator like this. Have a look at the ViewModelLocator, which makes it really easy to create the view models for a given view, for example, or pass the container to the UpdateWindow when it is created (somewhat ugly, too, though).

Prism RequestNavigate from PrismApplication immediately on start

In Prism 7 I can RegisterForNavigation and RequestNavigate from IModule like this:
public class ModuleAModule : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RequestNavigate("ContentRegion", "PersonList");
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<PersonList>();
}
}
and I know that I can RegisterForNavigation from PrismApplication like this:
public partial class App : PrismApplication
{
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<ViewA>();
}
}
but how can I RequestNavigate from PrismApplication immediately on start?
I have tried this:
public class MainWindowViewModel : BindableBase
{
public MainWindowViewModel(IRegionManager regionManager)
{
regionManager.RequestNavigate("ContentRegion", "ViewA");
}
}
but this.regions.Count is 0 in RegionManager from Prism
private IRegion GetRegionByName(string regionName)
{
return this.regions.FirstOrDefault(r => r.Name == regionName);
}
"ContentRegion" definitely exists, because it works if I try from the IModule and I know that RegisterTypes from PrismApplication executes before the MainWindowViewModel constructor.
I don't know what I am missing and I can't find the answer in any examples or tutorials.
Thank you for your help!
Your best bet is to override OnInitialized in your application and do the navigation there. You can access the container to fetch the region manager through the Container property.
If you use a bootstrapper, you can override InitializeModules and navigate there.

Is this a correct usage of Event Aggregation?

Dear Stackoverflow users,
I am currently developing a application that uses the MVVM pattern together with Caliburn micro.
I faced my first situation where I needed a dialog to be opened which would do some modifications to a ObservableCollection that needed to be visible on my main viewmodel.
I found out about event aggregation for my first time and thought that it would suite my situation. I am however not quite sure if event aggregation is the best solution or if I am using correctly... Therefore my question.
Please keep in mind that the namespaces are left there intentionally incase I got it all wrong.
The setup looks like this:
namespace Company.Product.Presentation.Core.Events
{
public class PersonCollectionChanged
{
[CanBeNull]
public ObservableCollection<Person> PersonCollection { get; set; }
public PersonCollectionChanged(ObservableCollection<Person> personCollection)
{
PersonCollection = personCollection;
}
}
}
ViewModel for the dialog
namespace Company.Product.Presentation.Modules.Selection
{
public class SelectionViewModel : Screen
{
private readonly IViewModelLoader _viewModelLoader;
private readonly IEventAggregator _eventAggregator;
private readonly ObservableCollection<Person> _tempPersonCollection;
private readonly PersonCollectionChanged _personCollectionChanged;
public SelectionViewModel(IViewModelLoader viewModelLoader,
IEventAggregator eventAggregator,
PersonCollectionChanged personCollectionChanged,
ObservableCollection<Person> tempPersonCollection)
{
_viewModelLoader = viewModelLoader;
_eventAggregator = eventAggregator;
_personCollectionChanged = personCollectionChanged;
_tempPersonCollection = tempPersonCollection;
AddPerson(new Person{Name = "Zatixiz"});
_personCollectionChanged.PersonCollection = _tempPersonCollection;
_eventAggregator.PublishOnUIThread(_personCollectionChanged);
}
internal void AddPerson(Person person)
{
_tempPersonCollection.Add(person);
}
}
}
Main viewmodel subscribing to the event:
namespace Company.Product.Presentation.Modules.Main
{
public class MainViewModel : BaseViewModel, IHandle<PersonCollectionChanged>
{
private ObservableCollection<Person> _mainPersonCollection;
public MainViewModel(
IViewModelLoader viewModelLoader,
IEventAggregator eventAggregator,
ObservableCollection<Person> mainPersonCollection) : base(viewModelLoader)
{
_mainPersonCOllection = mainPersonCollection;
eventAggregator.Subscribe(this);
}
public void Handle(PersonCollectionChanged message)
{
_mainPersonCollection = message;
}
}
}

Caliburn Event Aggregator with multiple views

I'm trying to build this project using Caliburn for the first time (and also the MEF structure, that I didn't fully understand).
I need to use both the Conductor and the EventAggregator.
The Conductor because i have an AppViewModel which "displays" 3 buttons that move the user to 3 different views (UserControls inside AppView).
And I need the EventAggregator because one of these 3 views has a button inside of it that must load 4th view (that must be a Window I think, not a UserControl, because it has to be full screen).
So I thought that when the user click this button inside the 3 view (UserControl inside AppView) a Message can be sent top the listener (that should be the AppViewModel), and this one should ActivateItem(4th vm).
I don't why but even following the examples of the projects of Caliburn my message does not reach the AppViewModel.
This is my bootstrapper:
public class AppBootstrapper : Bootstrapper<AppViewModel>
{
private CompositionContainer container;
protected override void Configure()
{
container = new CompositionContainer(new AggregateCatalog(AssemblySource.Instance.Select(x =>
new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()));
CompositionBatch batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(container);
container.Compose(batch);
}
protected override object GetInstance(Type serviceType, string key)
{
string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = container.GetExportedValues<object>(contract);
if (exports.Any())
return exports.First();
throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
}
protected override IEnumerable<object> GetAllInstances(Type serviceType)
{
return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
}
protected override void BuildUp(object instance)
{
container.SatisfyImportsOnce(instance);
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<AppViewModel>();
}
}
This is the AppViewModel:
[Export (typeof(AppViewModel))]
public class AppViewModel : Conductor<object>, IHandle<ChangeViewEvent>
{
[ImportingConstructor]
public AppViewModel(IEventAggregator events)
{
events.Subscribe(this);
ActivateItem(new MainViewModel());
}
public void GoToPatientsManager()
{
ActivateItem(new PatientsManagerViewModel(new WindowManager(), new EventAggregator()));
}
public void GoToTestManager()
{
ActivateItem(new TestManagerViewModel(new WindowManager()));
}
public void GoToResultsManager()
{
ActivateItem(new MainViewModel());
}
public void Handle(ChangeViewEvent message)
{
switch (message.ViewName)
{
case "TestManager" :
GoToTestManager();
break;
}
}
}
And this is the view model that should launch the request for loading the 4th vm
[Export(typeof(PatientsManagerViewModel))]
public class PatientsManagerViewModel : Screen
{
private readonly IWindowManager _windowManager;
private readonly IEventAggregator eventAggregator;
[ImportingConstructor]
public PatientsManagerViewModel(IWindowManager windowManager, IEventAggregator eventAggregator)
{
_windowManager = windowManager;
this.eventAggregator = eventAggregator;
}
#region Methods
public void ShowFakeMessage()
{
dynamic settings = new ExpandoObject();
settings.Placement = PlacementMode.Center;
settings.PlacementTarget = GetView(null);
var res = _windowManager.ShowDialog(new DeletePersonViewModel(), null, settings);
if (res)
{
// The result of the dialog men. In this true case we'll use Linq to delete the entry from the database
// using the dbContext
}
}
public void GoToTestManager()
{
eventAggregator.Publish(new ChangeViewEvent("TestManager"));
}
#endregion
}
It does not reach the Handle method of the AppViewModel.
Is these something wrong with the instances of the view models? I can't move forward from here...
EDIT
Can it be that the problem is that I pass a new EventAggregator object each time I activate a new PatientsManagerViewModel? Any tips?
You diagnosed your own problem. You are creating the PatientsManagerViewModel yourself and passing in a new event aggregator each time. The idea is that the event aggregator must be a singleton ie one instance shared amongst all of the view models and everything else. This is because the event aggregator stores the subscribers in memory and a new instance won't know who to inform of new events because nobody will have subscribed.
I would suggest that your AppViewModel.GoToPatientsManager() should look something like this:
public void GoToPatientsManager()
{
var patientManagerViewModel = IoC.Get<PatientsManagerViewModel>();
ActivateItem(patientManagerViewModel);
}
IoC is a (quite an ugly, hard to test) way you can access your container. You should not be creating your view models yourself, you should be getting the caliburn container do that for you instead. If the container creates your view models, then it will also fix up any dependencies that it has for you too, including the window manager and event aggregator.

Showing Prism modules inside Prism Module

I am implementing application in PRISM, which need to import modules dynamically from dll files.
I managed to do that - they are importing, but I can't display it.
I decided to create a special module to encapsulate it - let us call it ModuleDock.
So we have something like that:
Bootstrapper.cs:
class Bootstrapper : UnityBootstrapper
{
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void InitializeShell()
{
base.InitializeShell();
Application.Current.MainWindow = (Window)Shell;
Application.Current.MainWindow.Show();
}
protected override IModuleCatalog CreateModuleCatalog()
{
var modules = new DirectoryModuleCatalog
{
LoadSubdirectories = true,
ModulePath = #"C:\Modules"
};
modules.AddModule(typeof(ModuleDockModule));
return modules;
}
}
Shell.xaml:
<ContentControl regions:RegionManager.RegionName="ModuleDockModule" />
ModuleDockModule.cs (in ModuleDock project):
public class ModuleDockModule : IModule
{
private readonly IRegionManager _regionManager;
private readonly IUnityContainer _unityContainer;
public void Initialize()
{
RegisterIoc();
if (_regionManager.Regions.ContainsRegionWithName("ModuleDockModule"))
{
_regionManager.RegisterViewWithRegion("ModuleDockModule", typeof(ModuleDockView));
}
}
public ModuleDockModule(IRegionManager regionManager, IUnityContainer unityContainer, IRegionViewRegistry regionViewRegistry)
{
_regionManager = regionManager;
_unityContainer = unityContainer;
}
private void RegisterIoc()
{
_unityContainer.RegisterType<IModuleDockView, ModuleDockView>();
_unityContainer.RegisterType<IModuleDockViewModel, ModuleDockViewModel>();
}
}
and finally in one of loaded modules:
[Module(ModuleName = "TestModule", OnDemand = false)]
public class TestModuleModule : IModule
{
private readonly IRegionManager _regionManager;
private readonly IUnityContainer _unityContainer;
public void Initialize()
{
RegisterIoc();
if (_regionManager.Regions.ContainsRegionWithName("TestModule"))
{
_regionManager.RegisterViewWithRegion("TestModule", typeof(TestView));
}
}
public TestModuleModule(IRegionManager regionManager, IUnityContainer unityContainer)
{
_regionManager = regionManager;
_unityContainer = unityContainer;
}
private void RegisterIoc()
{
_unityContainer.RegisterType<ITestView, TestView>();
_unityContainer.RegisterType<ITestViewModel, TestViewModel>();
}
}
For test purposes I've created that line of XAML:
<ContentControl regions:RegionManager.RegionName="TestModule" />
Could you tell me, why that line displays TestModule in Shell.xaml, but don't display it in ModuleDockView.xaml?
Please, mind that in final stage I have to use various number of unknown modules provided by other users of my platform, so I can't make anything static (like module names or initializations).
Thank you in advance!
Based on the code you described, the TestModule module has a module dependency with ModuleDockModule as this is defining the region that the TestModule's Views would then be registered into.
Therefore, you would need to make sure that ModuleDockModule is initialized before TestModule and any other module that would depend on it. In order to do this, you would need to declare a dependency attribute on TestModuleModule class, right above the class definition as follows:
[Module(ModuleName = "TestModule", OnDemand = false)]
[ModuleDependency("ModuleDockModule")]
public class TestModuleModule : IModule
{ ...
For more information about Moduled you may refer to the following MSDN Prism Guide chapter:
Modular Application Development
I hope this helped you, Regards.

Categories

Resources