In Prism, how do I inject an interface into my module constuctor instead of a type? - wpf

In my prism application I'm getting the error Activation error occured while trying to get instance of type CustomerModule, key \"\".
It's caused by the fact that my customers module I'm trying to inject a "menuManager" of type IMenuManager:
namespace CustomerModule
{
public class CustomerModule : IModule
{
private readonly IRegionManager regionManager;
private readonly IUnityContainer container;
private readonly IMenuManager menuManager;
public CustomerModule(IUnityContainer container,
IRegionManager regionManager,
IMenuManager menuManager)
{
this.container = container;
this.regionManager = regionManager;
this.menuManager = menuManager;
}
public void Initialize()
{
container.RegisterType<IMenuManager, MenuManager>(new ContainerControlledLifetimeManager());
...
However, if I change the CustomerModule constructor to inject a type instead of an interface, then it works:
public CustomerModule(IUnityContainer container,
IRegionManager regionManager,
MenuManager menuManager)
So where do I need to register my MenuManager as implementing IMenuManager? It seems that registering it in CustomerModule's Initialize method is too late.
ANSWER:
I put it in ConfigureContainer() and it worked fine, be sure to leave in "base.ConfigureContainer()":
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType<MenuManager>(new ContainerControlledLifetimeManager());
}

Why would you ask for a MenuManager in the same module you are registering it in?
Unless you really think your MenuManager should be something provided by an external module, you might consider putting this registration in your bootstrapper if your modules will be dependent on it. It'd be something you'd put in your ConfigureContainer method of your bootstrapper.

As you've probably realised that's because the constructor is being called before the initialise method.
Two solutions are:
1) Have a ShellBootstrapper class in your Shell project with a method thats called when the program loads.
Have the method in the bootstrapper register any globabl Interfaces with your container.
2) Alternatively take the IMenuManager out of the constructor and just resolve it after you've registered it.
public void Initialize()
{
container.RegisterType<IMenuManager, MenuManager>(new ContainerControlledLifetimeManager());
this.menuManager = container.Resolve<IMenuManager>();
}
Hope this helps!

Related

How can I initialize a Prism module without View and ViewModel for working with EventAggregator?

I am writing an application using Prism that contains three modules. First one has a view to configure a "Person", second one is a service that generates that "Person" and third one is the visualization of all people. These three modules communicate with EventAggregator system. But I have problems with the messages on the service one.
In this service module I only have the service implementation and the module definition.
This service is a people manager that receives a message from EventAggregator, creates a "Person" with a task and send a message to the third module with this "Person".
Service:
private List<Person> people = new();
public PeopleControllerService(IEventAggregator eventAggregator, ICommonParametersService commonParameters)
{
this._eventAggregator = eventAggregator;
eventAggregator.GetEvent<GeneratePersonEvent>().Subscribe(GeneratePerson);
this._commonParameters = commonParameters;
}
private void GeneratePerson()
{
Person newPerson = new(this._commonParameters.DefaultPersonTask);
this.People.Add(newPerson);
this._eventAggregator.GetEvent<AssignedPersonEvent>().Publish(newPerson);
}
Module definition:
private PeopleControllerService moduleController;
public void OnInitialized(IContainerProvider containerProvider)
{
IEventAggregator eventAggregator = containerProvider.Resolve<IEventAggregator>();
ICommonParametersService commonParametersService = containerProvider.Resolve<ICommonParametersService>();
this.moduleController = new(eventAggregator, commonParametersService);
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
The problem is that when I send the "GeneratePersonEvent" message it never reaches the PeopleControllerService and the "GeneratePerson" method is never executed.
I've tried using a view and a viewModel, programming the service in the viewModel and assigning the view to a dummy and hidden region in the app and I've verified that it works that way.
Modified module definition:
public void OnInitialized(IContainerProvider containerProvider)
{
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RequestNavigate(RegionNames.DummyRegion, "PeopleController");
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<PeopleController>();
}
How can I use the EventAggregator without using a dummy view? Do I have to add something in the "RegisterTypes" method? I've tried with:
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<PeopleControllerService>();
}
but it doesn't work either.
I've checked this post: Can I get EventAggregator Subscribe Message without view, viewmodel in prism?, and there it says that it is possible, but doesn't describe how to implement.
Most of the time you want exactly one instance of a service, and you have to tell the container:
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<PeopleControllerService>();
}
Also, you want your service to implement an interface so that you can pass different implementations to the consumers of your service, the most obvious case is your tests.
You need to actually create the instance of your service, too. Normally, you inject it into some consumer, but if it's completely decoupled and only talks through the event aggregator, you have to create the instance manually:
// in App.xaml.cs
protected override void OnInitialized()
{
Container.Resolve<PeopleControllerService>();
base.OnInitialized();
}
Hint: if the service implements an interface, the application doesn't need to personally know the controller module.

CaliburnMicro IEventAggregator won't work with ChildViews

and sorry if this is a somewhat easy question, but I'm still new to CaliburnMicro and EventAggregator is proving to be the worst thing to learn by a long mile. Anyway, let's go to it. I have an app with a main ShellView and a bunch of ChildViews that display various information, and I need certain parameters to be shared between the Views. It did not take very long to find I need to use EventAggregator, but with Net6 I could not make it work, no chance. I found this app someone else did in Net3.1 and CaliburnMicro where a new window is created, on this second window there is a TextBox and a send button. Whatever you type here gets sended to the Main window. I studied the code and replicated the app succesfully, also with the latest version of Caliburn and Net6.
BUT then I decided to modify the app and instead of having a new window, now I have exactly the same but with a ChildView inside the ShellView, and here is where nothing works.
1st on the Bootstrapper
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container = new SimpleContainer();
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.PerRequest<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
var instance = _container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
As seen, I've created a SimpleContainer, then initialised it as a Singleton.
Also _container.Singleton<IWindowManager, WindowManager>(); is no longer needed as I am not opening a new window anymore, so that could be commented but as I had so many issues, I let it be just in case for this last try before posting this question.
2nd I've created a class for the message IHandle to manage.
public class EventMessage
{
public string Text { get; set; }
}
3rd I created and edited the ChildViewModel (still called SecondWindowViewModel as this experiment is directly derivated from the original worknig one) and ChildView (well SecondWindowView still). Note that SecondWindowView is a WPF User Control, not a Window.
class SecondWindowViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
private string _secondTextBox;
public string SecondTextBox
{
get { return _secondTextBox; }
set { _secondTextBox = value; NotifyOfPropertyChange(() => SecondTextBox); }
}
public SecondWindowViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator= eventAggregator;
}
public void SendBack()
{
EventMessage ToSend = new EventMessage();
ToSend.Text = SecondTextBox;
_eventAggregator.PublishOnUIThreadAsync(ToSend);
}
As seen, I have an IEventAggregator _eventAggregator, then on the constructor of the class I added this._eventAggregator= eventAggregator; and then on the method SendBack that is called upon pressing the button I send the message with SubscribeOnUIThread.
And lastly the ShellViewModel:
public class ShellViewModel : Conductor<Object>, IHandle<EventMessage>
{
private readonly IEventAggregator _eventAggregator;
private string _parentText;
public string ParentText
{
get { return _parentText; }
set { _parentText = value; NotifyOfPropertyChange(() => ParentText); }
}
public ShellViewModel(IEventAggregator eventAggregator)
{
ActivateItemAsync(new SecondWindowViewModel(_eventAggregator));
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public Task HandleAsync(EventMessage message, CancellationToken cancellationToken)
{
ParentText = message.Text;
return Task.CompletedTask;
}
//public void NewWindow()
//{
//WindowManager wm = new WindowManager();
//SecondWindowViewModel swm = new SecondWindowViewModel(_eventAggregator);
//wm.ShowWindowAsync(swm);
//}
}
}
Now here instead of inheriting from Screen and IScreen, I inherit from Conductor because I want to have that ChildView on my form. NewWindow is how it worked before but now that button no longer works as I don't need to launch a new window anymore, that's why it is commented out.
As seen, on the contructor I subscribe _eventAggregator, and then HandleAsync does the job of receiving the message and assign it to a variable. Now on the Caliburn Documentatin the method to use is public void Handle() but that no longer works, that's the only way I managed to make it work.
Now when I run this the app does load and seems to work just fine, but as soon as the SendBack() method gets called (in SecondWindowViewModel) the line _eventAggregator.PublishOnUIThreadAsync(ToSend); launches an exception System.NullReferenceException: 'Object reference not set to an instance of an object.'
From my understanding EventAggregator should not care if I'm sending the message to a Window or a user panel or anything. Only thnig I changed is commenting out NewWindow and deleting the old SecondWindowWiew WPF Window and replacing it with a new SecondWindowView WPF User Control with the exact same XAML, then in ShellWiev added bellow a <ContentControl x:Name="ActiveItem"/>.
I'm a bit of a loss here, I've been trying everything, coping the code from the documentation, looking for tutorials online, other StackOverflow questions, and while I could make the UI load and make HandleAsync work, for 3 days straight I could not make it work with ChildViews. That code I wrote does work with a new window. I even run into problems of ShellView straight up not loading unless putting a new constructor that takes no parameters and empty inside, but that's for another day.
Sorry for the extra long post, but I think it's important to put all the information out there. Thank you for your time and again, sorry if this is somewhat of a dumb question, but we all have to start somewhere no?

How to load module when using events

I'm very newbie in C#\Prism ecosystem.
I want to connect modules via events, but if just send event:
_eventAggregator.GetEvent<LoginSuccessEvent>().Publish(new LoginSuccessEventArgs(user));
then my event handler is not working.
As I understand it happens, because reciever ViewModel is not created (I checked with break point in debugger).
But if I navigate from event sender:
_regionManager.RequestNavigate(RegionNames.RootRegion, "WorksheetListView");
_regionManager.RequestNavigate(RegionNames.WorksheetDetailsRegion, "WorksheetDetailsView");
_eventAggregator.GetEvent<LoginSuccessEvent>().Publish(new LoginSuccessEventArgs(user));
Then before first RequestNavigate command reciever ViewModel constructor is called.
_regionManager = regionManager;
_model = container.Resolve<WorksheetListModel>();
OpenWorksheetCommand = new DelegateCommand(OpenWorksheet);
Worksheets = _model.WorksheetList;
eventAggregator.GetEvent<LoginSuccessEvent>().Subscribe(OnLoginSuccessEvent);
I tried to add ViewModel class registration to reciever module:
_container.RegisterType<WorksheetListViewModel>();
But no luck. I don't want to add this registration to sender, because hard relation is maked.
But I want to have weak relation between modulel and to do navigation from RECIEVER, but not from SENDER. So sender will don't know anything about reciever.
How can I achive this?
Thanks.
It's a bit unclear what you're trying to achieve, but as far as I get it, your problem's that events are there for those instances that are currently alive. It seems you want more of a state, so that view models that are created after the user logged in can check whether a user is logged in and act accordingly.
I suggest you create a service to hold the currently logged in user and keep the event, because they complement each other nicely.
Example:
public interface IUserManager : INotifyPropertyChanged // this is optional if you keep the event
{
// returns null if no user is logged in
string CurrentUserName { get; }
// returns true if user name and password are valid and updates CurrentUserName
bool TryLogin( string userName, string password );
}
Ok. I found a tons of question like ('EventAggregator don't work around modules').
My solution is very easy. I create a instance of my viewmodel, so constructor with event subscribtion is invoked too.
using Microsoft.Practices.Unity;
using Prism.Modularity;
using Prism.Regions;
using Prism.Events;
using Prism.Unity;
using WorksheetListModule.ViewModels;
using WorksheetListModule.Views;
using WorksheetListModule.Models;
namespace WorksheetListModule
{
public class WorksheetListModule : IModule
{
IRegionManager _regionManager;
IUnityContainer _container;
IEventAggregator _eventAggregator;
public WorksheetListModule(RegionManager regionManager, IUnityContainer container, IEventAggregator eventAggregator)
{
_regionManager = regionManager;
_container = container;
_eventAggregator = eventAggregator;
}
public void Initialize()
{
WorksheetListViewModel vm = new WorksheetListViewModel(_regionManager, _container, _eventAggregator);
_container.RegisterInstance<WorksheetListViewModel>(vm);
_container.RegisterType<WorksheetListModel>();
_container.RegisterTypeForNavigation<WorksheetListView>();
_container.RegisterTypeForNavigation<WorksheetDetailsView>();
}
}
}
Key feature here is RegisterInstance function.
So now I can do navigation in event reciever and event sender now don't know any information about reciever internal structure.

Modules registration in Prism 7.1

In Prism 7.1, the IModule interface has changed from version 6.3, and now exposes the two methods RegisterTypes (IContainerRegistry containerRegistry) and OnInitialized (IContainerProvider containerProvider). I ask forgiveness, but I cannot understand how I have to register the Views implemented in the Module. it's probably so simple that I can not see the solution to the problem. Can you give me an example to finally make me understand how I should do? Wherever I looked, I found only examples regarding version 6.3, which I know rather well ..
Prism 7.X introduces an abstraction around the DI Container. There were a number of reasons for this but the top two are:
Many of the support questions from the community to the Prism team revolved around how to do something with a container that has nothing to do with the Prism team.
By abstracting the container it makes scenarios around sharing code and swapping out containers easier.
It's also important to understand that by abstracting the Container we've also made changes to the Container Extensions responsible for registering Views. Specifically we now have them on the IContainerRegistry. So given the sample Prism 6.X Module here:
public class ModuleA
{
private IUnityContainer _container { get; }
public ModuleA(IUnityContainer container)
{
_container = container;
}
public void Initialize()
{
// register stuff
_container.RegisterViewForNavigation<ViewA>();
// Setup Event listeners etc...
var ea = _container.Resolve<IEventAggregator>();
}
}
We would update this to:
public class ModuleA
{
public void OnInitialized(IContainerProvider containerProvider)
{
// Setup Event listeners etc...
var ea = containerProvider.Resolve<IEventAggregator>();
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
// register stuff
containerRegistry.RegisterForNavigation<ViewA>();
}
}
it's probably so simple that I can not see the solution to the problem
Just use RegisterTypes - registering a view is registering a type, after all.

Register views with regions depending on the user rights in Silverlight

We have a module class which implements the IModule interface and it has only one method Initialize() in which we register our views.
Is it possible to register these views after successful login?
I want to prohibit the registration of several views depending on the current user. But the user logs on after the module initialization.
Is there a way to provide a callback where Prism can evaluate if the registration is active? Or do I have the chance to disable registrations of the Region Manager? Any other ideas?
Thanks
The easiest way to communicate among multiple modules (or even within) in Prism is using the EventAggregator. Here is what I would do:
Create a CompositePresentationEvent for UserLoginEvent that takes relevant parameter regarding the user
Publish the event using IEventAggregator when the user successfully logs in
On your module initialize, subscribe to the UserLoginEvent, in the handler register the appropriate views
Repeat the above, but opposite, for UserLogout if desired.
In your infrastructure lib:
public class UserLoginEvent : CompositePresentationEvent<User> { }
Then in your module:
public class YourModule : IModule
{
private readonly IUnityContainer container;
private readonly IRegionManager regionManager;
private readonly IEventAggregator events;
public YourModule(IUnityContainer container, IRegionManager manager, IEventAggregator events)
{
this.container = container;
this.regionManager = manager;
this.events = events;
}
public void Initialize()
{
....
events.GetEvent<UserLoginEvent>().Subscribe(RegisterUserViews);
}
private void RegisterUserViews(User u)
{
// check user permissions
// register and create views using container, regionManager
}
}
In whatever module / code your user logs in... I assume you can get the IEventAggregator (similar to above) and then do something like:
OnUserLogin(User u)
{
eventAggregator.GetEvent<UserLoginEvent>().Publish(u);
}
Hope this helps! if a user logs out, then you may want to store references to your views in your module and remove them on a UserLogoutEvent which would work like the above code but just doing the opposite.

Resources