(4/7).NET Core 3 WPF MVVM Framework Prism Series: Event Aggregator

(4/7).NET Core 3 WPF MVVM Framework Prism Series: Event Aggregator

How to use the event aggregator of the MVVM framework Prism to implement communication between modules in .NET Core 3 environment.

Last updated 6/11/2023 12:09 AM
RyzenAdorer
8 min read
Category
WPF
Topic
WPF MVVM Framework Prism Series
Tags
.NET C# WPF Prism MVVM

This article is reproduced from

Original Author: RyzenAdorer

Original Title: .NET Core 3 WPF MVVM Framework Prism Series: Event Aggregator

Original Link: https://www.cnblogs.com/ryzen/p/12196619.html

This article will introduce how to use the Event Aggregator of the Prism MVVM framework to implement communication between modules in a .NET Core 3 environment.

1. Event Aggregator

In the previous article .NET Core 3 WPF MVVM Framework Prism Series: Modularization we left some questions: how to handle communication between different forms within the same module and between different forms across different modules. Prism provides an event mechanism that allows low-coupling communication between modules in the application. This mechanism is based on the Event Aggregator service, enabling communication between publishers and subscribers through events without direct references between them, thus achieving a low-coupling communication method between modules. Below is an official model diagram of the Event Aggregator:

2. Creating and Publishing Events

2.1. Creating an Event

First, let's handle communication between different forms within the same module. Create a new folder Events under PrismMetroSample.Infrastructure, then create a new class PatientSentEvent with the following code:

PatientSentEvent.cs:

public class PatientSentEvent: PubSubEvent<Patient>
{
}

2.2. Subscribing to the Event

Then, subscribe to this event in the PatientDetailViewModel class of the patient detail form. The code is as follows:

PatientDetailViewModel.cs:

 public class PatientDetailViewModel : BindableBase
 {
    IEventAggregator _ea;
    IMedicineSerivce _medicineSerivce;

    private Patient _currentPatient;
     // Current patient
    public Patient CurrentPatient
    {
        get { return _currentPatient; }
        set { SetProperty(ref _currentPatient, value); }
    }

    private ObservableCollection<Medicine> _lstMedicines;
     // Medicine list of the current patient
    public ObservableCollection<Medicine> lstMedicines
    {
        get { return _lstMedicines; }
        set { SetProperty(ref _lstMedicines, value); }
    }

     // Constructor
    public PatientDetailViewModel(IEventAggregator ea, IMedicineSerivce medicineSerivce)
    {
        _medicineSerivce = medicineSerivce;
        _ea = ea;
        _ea.GetEvent<PatientSentEvent>().Subscribe(PatientMessageReceived); // Subscribe to the event
    }

     // Handler function for receiving messages
    private void PatientMessageReceived(Patient patient)
    {
        this.CurrentPatient = patient;
        this.lstMedicines = new ObservableCollection<Medicine>(_medicineSerivce.GetRecipesByPatientId(this.CurrentPatient.Id).FirstOrDefault().LstMedicines);
        }
    }

2.3. Publishing the Event

Then, publish the event in the PatientListViewModel of the patient list form. The code is as follows:

PatientListViewModel.cs:

public class PatientListViewModel : BindableBase
{

    private IApplicationCommands _applicationCommands;
    public IApplicationCommands ApplicationCommands
    {
        get { return _applicationCommands; }
        set { SetProperty(ref _applicationCommands, value); }
    }

    private List<Patient> _allPatients;
    public List<Patient> AllPatients
    {
        get { return _allPatients; }
        set { SetProperty(ref _allPatients, value); }
    }

    private DelegateCommand<Patient> _mouseDoubleClickCommand;
    public DelegateCommand<Patient> MouseDoubleClickCommand =>
        _mouseDoubleClickCommand ?? (_mouseDoubleClickCommand = new DelegateCommand<Patient>(ExecuteMouseDoubleClickCommand));

    IEventAggregator _ea;

    IPatientService _patientService;

        /// <summary>
        /// Constructor
        /// </summary>
    public PatientListViewModel(IPatientService patientService, IEventAggregator ea, IApplicationCommands applicationCommands)
    {
         _ea = ea;
         this.ApplicationCommands = applicationCommands;
         _patientService = patientService;
         this.AllPatients = _patientService.GetAllPatients();
    }

    /// <summary>
    /// DataGrid double-click command method
    /// </summary>
    void ExecuteMouseDoubleClickCommand(Patient patient)
    {
        // Open the form
        this.ApplicationCommands.ShowCommand.Execute(FlyoutNames.PatientDetailFlyout);
        // Publish the event
        _ea.GetEvent<PatientSentEvent>().Publish(patient);
    }

}

Effect:

2.4. Implementing Multiple Subscriptions and Publications

Similarly, adding the searched Medicine to the current patient list follows the same steps. Create an event class MedicineSentEvent in the Events folder:

MedicineSentEvent.cs:

 public class MedicineSentEvent: PubSubEvent<Medicine>
 {

 }

Subscribe to this event in the PatientDetailViewModel of the patient detail form:

PatientDetailViewModel.cs:

 public PatientDetailViewModel(IEventAggregator ea, IMedicineSerivce medicineSerivce)
 {
      _medicineSerivce = medicineSerivce;
      _ea = ea;
      _ea.GetEvent<PatientSentEvent>().Subscribe(PatientMessageReceived);
      _ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived);
 }

 /// <summary>
 // Handler function for receiving event messages
 /// </summary>
 private void MedicineMessageReceived(Medicine  medicine)
 {
      this.lstMedicines?.Add(medicine);
 }

Also subscribe to this event in the MedicineMainContentViewModel of the medicine list form:

MedicineMainContentViewModel.cs:

public class MedicineMainContentViewModel : BindableBase
{
   IMedicineSerivce _medicineSerivce;
   IEventAggregator _ea;

   private ObservableCollection<Medicine> _allMedicines;
   public ObservableCollection<Medicine> AllMedicines
   {
        get { return _allMedicines; }
        set { SetProperty(ref _allMedicines, value); }
   }
   public MedicineMainContentViewModel(IMedicineSerivce medicineSerivce,IEventAggregator ea)
   {
        _medicineSerivce = medicineSerivce;
        _ea = ea;
        this.AllMedicines = new ObservableCollection<Medicine>(_medicineSerivce.GetAllMedicines());
        _ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived); // Subscribe to the event
   }

   /// <summary>
   /// Event message handler function
   /// </summary>
   private void MedicineMessageReceived(Medicine medicine)
   {
        this.AllMedicines?.Add(medicine);
   }
}

Publish the event message in the SearchMedicineViewModel of the search medicine form:

SearchMedicineViewModel.cs:

 IEventAggregator _ea;

 private DelegateCommand<Medicine> _addMedicineCommand;
 public DelegateCommand<Medicine> AddMedicineCommand =>
     _addMedicineCommand ?? (_addMedicineCommand = new DelegateCommand<Medicine>(ExecuteAddMedicineCommand));

public SearchMedicineViewModel(IMedicineSerivce medicineSerivce, IEventAggregator ea)
{
     _ea = ea;
     _medicineSerivce = medicineSerivce;
     this.CurrentMedicines = this.AllMedicines = _medicineSerivce.GetAllMedicines();
 }

 void ExecuteAddMedicineCommand(Medicine currentMedicine)
 {
     _ea.GetEvent<MedicineSentEvent>().Publish(currentMedicine); // Publish the event
 }

Effect:

Now let's look at the event model and assembly references of the demo project, as shown below:

We can see that the PatientModule and MedicineModule modules communicate without referencing each other. They achieve indirect dependency by referencing the PrismMetroSample.Infrastructure assembly, enabling communication between different modules with low coupling.

3. Unsubscribing from Events

Prism also provides the ability to unsubscribe. We provide this functionality in the patient detail form. Add the following lines to PatientDetailViewModel:

PatientDetailViewModel.cs:

 private DelegateCommand _cancleSubscribeCommand;
 public DelegateCommand CancleSubscribeCommand =>
       _cancleSubscribeCommand ?? (_cancleSubscribeCommand = new DelegateCommand(ExecuteCancleSubscribeCommand));

  void ExecuteCancleSubscribeCommand()
  {
      _ea.GetEvent<MedicineSentEvent>().Unsubscribe(MedicineMessageReceived);
  }

Effect:

4. Several Subscription Modes

In the demo, we have implemented communication between subscribers and publishers through the event mechanism of the Event Aggregator. Let's take a closer look at the subscription modes provided by Prism. We can explain them through the Subscribe method of the PubSubEvent class, which has the most parameters overload:

Subscribe.cs:

public virtual SubscriptionToken Subscribe(Action<TPayload> action, ThreadOption threadOption, bool keepSubscriberReferenceAlive, Predicate<TPayload> filter);

4.1. action Parameter

The action parameter is the function that receives the message.

4.2. threadOption Parameter

The ThreadOption type parameter threadOption is an enumeration type parameter. The code is as follows:

ThreadOption.cs:

public enum ThreadOption
{
        /// <summary>
        /// The call is done on the same thread on which the <see    cref="PubSubEvent{TPayload}"/> was published.
        /// </summary>
        PublisherThread,

        /// <summary>
        /// The call is done on the UI thread.
        /// </summary>
        UIThread,

        /// <summary>
        /// The call is done asynchronously on a background thread.
        /// </summary>
        BackgroundThread
}

The three enumeration values:

  • PublisherThread: default setting. Use this to receive messages from the publisher on the same thread.
  • UIThread: Allows receiving events on the UI thread.
  • BackgroundThread: Allows receiving events asynchronously on the thread pool.

4.3. keepSubscriberReferenceAlive Parameter

The default keepSubscriberReferenceAlive is false. According to the Prism documentation, this parameter indicates whether the subscription uses a weak reference or a strong reference. false means weak reference, true means strong reference:

  • When set to true, it can improve the performance of publishing multiple events in a short time, but you must manually unsubscribe from the event because the event instance holds a strong reference to the subscriber instance. Otherwise, even if the form is closed, it will not be GC-collected.
  • When set to false, the event maintains a weak reference to the subscriber instance. When the form is closed, the event is automatically unsubscribed, meaning you do not need to manually unsubscribe.

4.4. filter Parameter

The filter is a Predicate<T> generic delegate parameter with a boolean return value. It can be used for subscription filtering. Using our demo as an example, modify the subscription in PatientDetailViewModel:

PatientDetailViewModel.cs:

  _ea.GetEvent<MedicineSentEvent>().Subscribe(MedicineMessageReceived,
ThreadOption.PublisherThread,false,medicine=>medicine.Name=="当归"|| medicine.Name== "琼浆玉露");

Effect:

5. Source Code

Finally, attach the source code of the entire demo: PrismDemo Source Code

Keep Exploring

Related Reading

More Articles