Example 08: Event-Driven Messaging System
Overview
The dashboard implements a comprehensive event-driven architecture that enables loose coupling between components while maintaining responsive UI updates and coordinated workflows.
Custom Event System
Event Definitions
// NotificationEventArgs.cs
public class NotificationEventArgs : EventArgs
{
public string Title { get; set; }
public string Message { get; set; }
public NotificationType Type { get; set; }
public DateTime Timestamp { get; set; }
public object Data { get; set; }
public NotificationEventArgs(string title, string message,
NotificationType type = NotificationType.Information)
{
Title = title;
Message = message;
Type = type;
Timestamp = DateTime.Now;
}
}
// SelectedItemsChangedEventArgs.cs
public class SelectedItemsChangedEventArgs<T> : EventArgs
{
public IEnumerable<T> AddedItems { get; set; }
public IEnumerable<T> RemovedItems { get; set; }
public IEnumerable<T> AllSelectedItems { get; set; }
public int TotalCount { get; set; }
public SelectedItemsChangedEventArgs(
IEnumerable<T> added,
IEnumerable<T> removed,
IEnumerable<T> all)
{
AddedItems = added;
RemovedItems = removed;
AllSelectedItems = all;
TotalCount = all?.Count() ?? 0;
}
}
// LaunchSysproProgramEventArgs.cs
public class LaunchSysproProgramEventArgs : EventArgs
{
public string ProgramCode { get; set; }
public Dictionary<string, string> Parameters { get; set; }
public bool WaitForExit { get; set; }
public Action<int> ExitCallback { get; set; }
}
Event Aggregator Pattern
public interface IEventAggregator
{
void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : EventArgs;
void Unsubscribe<TEvent>(Action<TEvent> handler) where TEvent : EventArgs;
void Publish<TEvent>(TEvent eventArgs) where TEvent : EventArgs;
}
public class EventAggregator : IEventAggregator
{
private readonly Dictionary<Type, List<Delegate>> _handlers = new();
private readonly object _lock = new();
public void Subscribe<TEvent>(Action<TEvent> handler) where TEvent : EventArgs
{
lock (_lock)
{
var type = typeof(TEvent);
if (!_handlers.ContainsKey(type))
_handlers[type] = new List<Delegate>();
_handlers[type].Add(handler);
}
}
public void Publish<TEvent>(TEvent eventArgs) where TEvent : EventArgs
{
List<Delegate> handlers;
lock (_lock)
{
var type = typeof(TEvent);
if (!_handlers.ContainsKey(type))
return;
handlers = _handlers[type].ToList(); // Copy to avoid lock during invocation
}
foreach (var handler in handlers)
{
try
{
(handler as Action<TEvent>)?.Invoke(eventArgs);
}
catch (Exception ex)
{
// Log but don't crash
_logger.LogError(ex, "Error in event handler");
}
}
}
}
Cross-Component Communication
ViewModel to ViewModel
// Publisher ViewModel
public class WarehouseOrderingViewModel : BaseViewModel
{
private readonly IEventAggregator _eventAggregator;
private void OnSupplierSelected(SupplierListItem supplier)
{
_eventAggregator.Publish(new SupplierSelectedEventArgs
{
Supplier = supplier,
Source = this
});
}
}
// Subscriber ViewModel
public class OrderSummaryViewModel : BaseViewModel
{
public OrderSummaryViewModel(IEventAggregator eventAggregator)
{
eventAggregator.Subscribe<SupplierSelectedEventArgs>(OnSupplierSelected);
}
private void OnSupplierSelected(SupplierSelectedEventArgs e)
{
// React to supplier selection
UpdateOrdersForSupplier(e.Supplier);
}
}
Service to UI Updates
public class OrderProcessingService
{
private readonly IEventAggregator _eventAggregator;
public async Task ProcessOrdersAsync(IEnumerable<Order> orders)
{
var total = orders.Count();
var current = 0;
foreach (var order in orders)
{
current++;
// Publish progress
_eventAggregator.Publish(new ProgressEventArgs
{
Current = current,
Total = total,
Message = $"Processing order {order.OrderNumber}"
});
try
{
await ProcessOrder(order);
// Publish success
_eventAggregator.Publish(new OrderProcessedEventArgs
{
Order = order,
Success = true
});
}
catch (Exception ex)
{
// Publish failure
_eventAggregator.Publish(new OrderProcessedEventArgs
{
Order = order,
Success = false,
Error = ex.Message
});
}
}
}
}
Event Sequencing
Coordinated Workflows
public class WorkflowCoordinator
{
private readonly IEventAggregator _eventAggregator;
private readonly Queue<WorkflowStep> _steps = new();
public void StartWorkflow()
{
_eventAggregator.Subscribe<StepCompletedEventArgs>(OnStepCompleted);
ExecuteNextStep();
}
private void OnStepCompleted(StepCompletedEventArgs e)
{
if (!e.Success)
{
HandleWorkflowError(e);
return;
}
ExecuteNextStep();
}
private void ExecuteNextStep()
{
if (!_steps.Any())
{
_eventAggregator.Publish(new WorkflowCompletedEventArgs());
return;
}
var step = _steps.Dequeue();
step.Execute();
}
}
Weak Event Pattern
Memory Leak Prevention
public class WeakEventManager<TEventArgs> where TEventArgs : EventArgs
{
private readonly List<WeakReference> _handlers = new();
public void AddHandler(EventHandler<TEventArgs> handler)
{
_handlers.Add(new WeakReference(handler));
}
public void RemoveHandler(EventHandler<TEventArgs> handler)
{
_handlers.RemoveAll(wr => !wr.IsAlive || wr.Target == handler);
}
public void RaiseEvent(object sender, TEventArgs args)
{
// Clean up dead references
_handlers.RemoveAll(wr => !wr.IsAlive);
// Invoke live handlers
foreach (var wr in _handlers)
{
if (wr.Target is EventHandler<TEventArgs> handler)
{
handler(sender, args);
}
}
}
}
Async Event Handling
Non-Blocking Events
public class AsyncEventAggregator
{
public async Task PublishAsync<TEvent>(TEvent eventArgs)
where TEvent : EventArgs
{
var handlers = GetHandlers<TEvent>();
var tasks = handlers.Select(handler =>
Task.Run(() => handler.Invoke(eventArgs)));
await Task.WhenAll(tasks);
}
public void PublishAndForget<TEvent>(TEvent eventArgs)
where TEvent : EventArgs
{
Task.Run(async () =>
{
try
{
await PublishAsync(eventArgs);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in async event publishing");
}
});
}
}
Benefits
- Loose coupling between components
- Responsive UI updates
- Centralized event handling
- Easy to test and mock
- Scalable architecture
- Memory leak prevention