Hi, congratulations for this project !
I want to add communication between viewmodels without event (like mvvmlight messenger).
I've implemeted a solution and I would like your opinion.
The main principles are :
When I publish a notification of type T where T : IVMNotification, all actives VMs that implents IHandleVMNotification are notified.
public class AppVM : BaseVM, IRoutable, IHandleVMNotification<ShowMessageRequested>
{ ....
public void Handle(ShowMessageRequested notification)
{....}
}
I use MediatR to publish/handle notifications.
vm can publish notification with mediator instance injected from the constructor.
public class BaseMediatrVM : BaseVM
{
private readonly IMediator _mediator;
...
public BaseMediatrVM(IMediator mediator)
{
_mediator = mediator ?? throw new ArgumentNullException(nameof(mediator));
}
protected void Publish(VMNotificationBase notification)
{
...
_mediator.Publish(notification);
}
}
public class HelloWorldVM : BaseMediatrVM, IHelloWorldState
{
...
public string Greetings
{
get => Get<string>();
protected set
{
Set(value);
Publish(new ShowMessageRequested(value));
}
}
public HelloWorldVM(IMediator mediator):base(mediator)
{
...
}
...
}
Then my handler of type MediatR.INotificationHandler handle the notification.
Here begins questions ! :)
The idee is to broadcast the notification by using DotNetify.IVMControllerFactory injected from the handler's constructor.
DotNetify.IVMControllerFactory call the good VMController
The VMController broadcast the notification to actives VM interested by the message
First problem, I need to have the SignalR connectionId to be able to get the good VMController.
To solve that, I've added ContextKey property to VMBase
/// <summary>
/// The key of the vmController parent
/// </summary>
[Ignore]
public virtual string ContextKey { get; set; }
and set the value in VMController.CreateVM(...). Now I can publish notification containing the ContextKey.
My final VMController :
/// <summary>
/// This class manages instantiations and updates of view models as requested by browser clients.
/// </summary>
public class VMControllerWithNotificationBroadcast : VMController {
/// <summary>
/// Constructor.
/// </summary>
/// <param name="vmResponse">Function invoked by the view model to provide response back to the client.</param>
/// <param name="key">Identifies the object.</param>
public VMControllerWithNotificationBroadcast(VMResponseDelegate vmResponse, string key):base(vmResponse,key){ }
/// <summary>
/// Creates a view model.
/// </summary>
/// <param name="vmId">Identifies the view model.</param>
/// <param name="vmArg">Optional view model's initialization argument.</param>
/// <returns>View model instance.</returns>
protected override BaseVM CreateVM(string vmId, object vmArg = null)
{
var vmInstance = base.CreateVM(vmId, vmArg);
vmInstance.ContextKey = _key;
return vmInstance;
}
/// <summary>
/// Broadcast the notification to all the concerned actives vm.
/// </summary>
/// <typeparam name="T">Type of the notification to broadcast</typeparam>
/// <param name="notification">The notification to broadcast</param>
public void BroadcastNotification<T>(Notification.IVMNotification notification) where T : Notification.IVMNotification
{
foreach (var item in GetActiveVMsOfType<Notification.IHandleVMNotification<T>>())
item.Handle((T)notification);
}
/// return all actives VM of type T
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
protected IEnumerable<T> GetActiveVMsOfType<T>() where T : class
{
return _activeVMs.Where(vm => vm.Value.Instance is T).Select(vm => vm.Value.Instance as T);
}
}
My final VMFactory:
/// <summary>
/// Provides view model controllers.
/// </summary>
public class VMControllerFactoryWithBroadcast : VMControllerFactory
{
public VMControllerFactoryWithBroadcast(IMemoryCache memoryCache) : base(memoryCache)
{
}
/// <summary>
/// Creates a view model controller and assigns it a key.
/// On subsequent calls, use the same key to return the same object.
/// </summary>
/// <param name="key">Identifies the object.</param>
/// <returns>View model controller.</returns>
public override VMController GetInstance(string key)
{
var cache = _controllersCache;
if (!cache.TryGetValue(key, out Lazy<VMControllerWithNotificationBroadcast> cachedValue))
{
cachedValue = new Lazy<VMControllerWithNotificationBroadcast>(() => new VMControllerWithNotificationBroadcast(ResponseDelegate, key));
cache.Set(key, cachedValue, GetCacheEntryOptions());
}
return cachedValue?.Value;
}
/// <summary>
/// Broadcast the notification to the vmController identified by notification.ContextId.
/// </summary>
/// <typeparam name="T">Type of the notification to broadcast</typeparam>
/// <param name="notification">The notification to broadcast</param>
public void BroadcastNotification<T>(Notification.IVMNotification notification) where T : Notification.IVMNotification
{
var vmController = (VMControllerWithNotificationBroadcast)GetInstance(notification.ContextId);
if (vmController == null)
throw new Exception($"No vmController find for key '{notification.ContextId}'");
vmController.BroadcastNotification<T>(notification);
}
}
I had to make some changes in base class to override what I needed.
I've tried to be the less intrusive.
I hope I'm clear. and you understand my english.
yes, I managed to publish messages between the view model but the solution involves changes to the library code.
Have another solution ?