C# View Model Messenger Service

C# View Model Messenger Service

When building a WPF application using the Inversion of Control (IOC) Principle with dependency injection we often need to inject view models into view models so we can share the data, events or property’s. This tightly couples the view models together which violates the IOC principle. To get around this we want to use the Messenger Service pattern to communicate between the view models and share data.

Messenger Service

Why Use a Custom Messaging System?
Off-the-shelf messaging solutions may not always align perfectly with the requirements of your web application. By developing a custom messaging system, you gain full control over how messages are sent, received, and processed. This enables you to tailor the messaging system to the specific needs of your application, resulting in better performance and scalability.

Key Components of the Messaging System

  • Messenger Class: The heart of the messaging system, the Messenger class provides methods for sending and subscribing to messages of various types.
  • Subscription Record: A simple record type that encapsulates a subscriber object and the action to be performed when a message is received.

Functionality Overview

  1. Sending Messages: The Send method allows messages of any type to be sent to subscribers. It updates the current state of the message and notifies all subscribers associated with the message type.
  2. Subscribing to Messages: The Subscribe method enables objects to subscribe to messages of a specific type. Subscribers provide an action that is executed when a message of the subscribed type is received.
  3. Unsubscribing from Messages: Subscribers can unsubscribe from receiving messages of a particular type using the Unsubscribe method.

Integration with WPF Applications

  • Real-time Updates: Integrate the messaging system to provide real-time updates to users, such as live chat features, notifications, or dynamic content updates.
  • Event Triggering: Use the messaging system to trigger events based on user actions or system events, enhancing the interactivity of your web application.
  • Customized Interactions: Tailor the messaging system to meet the specific requirements of your web application, ensuring seamless integration and optimal performance.

Messenger Service Code

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;

public interface IMessenger
{
    void Send<TMessage>(TMessage message);
    void Subscribe<TMessage>(object subscriber, Action<object> action);
    void UnSubscribe<TMessage>(object subscriber);
}
/// <summary>
/// Defines a simple messaging system for sending and receiving messages of various types.
/// </summary>
public class Messenger : IMessenger
{
    // Dictionary to hold subscriptions for different message types
    private ConcurrentDictionary<Type, SynchronizedCollection<Subscription>> _subscriptions = new ConcurrentDictionary<Type, SynchronizedCollection<Subscription>>();

    // Dictionary to hold the current state of messages
    private ConcurrentDictionary<Type, object> _currentState = new ConcurrentDictionary<Type, object>();

    /// <summary>
    /// Initializes a new instance of the Messenger class.
    /// </summary>
    public Messenger() { }

    /// <summary>
    /// Sends a message of type TMessage to all subscribers.
    /// </summary>
    /// <typeparam name="TMessage">The type of the message being sent.</typeparam>
    /// <param name="message">The message to send.</param>
    public void Send<TMessage>(TMessage message)
    {
        if (message == null)
            throw new ArgumentNullException(nameof(message));

        // Add or update the current state for the message type
        _currentState.AddOrUpdate(typeof(TMessage), (o) => message, (o, old) => message);

        // Invoke each subscriber's action for the given message type
        if (_subscriptions.TryGetValue(typeof(TMessage), out var subscriptions))
        {
            foreach (var subscription in subscriptions)
            {
                subscription.action(message);
            }
        }
    }

    /// <summary>
    /// Subscribes an object to receive messages of type TMessage.
    /// </summary>
    /// <typeparam name="TMessage">The type of the message to subscribe to.</typeparam>
    /// <param name="subscriber">The object subscribing to the message.</param>
    /// <param name="action">The action to perform when a message is received.</param>
    public void Subscribe<TMessage>(object subscriber, Action<object> action)
    {
        // Create a new subscription
        Subscription newSubscriber = new Subscription(subscriber, action);

        // Add the subscription to the list of subscriptions for the message type
        var subscriptions = _subscriptions.GetOrAdd(typeof(TMessage), new SynchronizedCollection<Subscription>());
        subscriptions.Add(newSubscriber);

        // If there's a current state for the message type, trigger the action immediately
        if (_currentState.TryGetValue(typeof(TMessage), out var currentState))
        {
            action(currentState);
        }
    }

    /// <summary>
    /// Unsubscribes an object from receiving messages of type TMessage.
    /// </summary>
    /// <typeparam name="TMessage">The type of the message to unsubscribe from.</typeparam>
    /// <param name="subscriber">The object to unsubscribe.</param>
    public void UnSubscribe<TMessage>(object subscriber)
    {
        // Check if there are subscriptions for the message type
        if (_subscriptions.TryGetValue(typeof(TMessage), out var subscriptions))
        {
            // Find and remove the subscription associated with the subscriber
            var subscriptionToRemove = subscriptions.FirstOrDefault(s => s.subscriber == subscriber);
            if (subscriptionToRemove != null)
            {
                subscriptions.Remove(subscriptionToRemove);
            }
        }
    }

    /// <summary>
    /// Represents a subscription to a message type.
    /// </summary>
    public record Subscription(object subscriber, Action<object> action);
}

Integrating into a WPF Application

See my post on dependency injection if you need some help.

App.Xaml.cs

In the services section of the App.XAML.cs file we need to AddSingleton for the Messenger service as shown below.

 _host = Host.CreateDefaultBuilder()
     .ConfigureServices((context, services) =>
     {
         services.AddSingleton(m => new MainWindow());
         services.AddSingleton(_logger);
         services.AddSingleton<IMainViewModel, MainViewModel>();
         services.AddSingleton<IMessenger, Messenger>();
     })
     .UseSerilog()
     .Build();

Message Records

I added a folder for messages and created a new class called MessageRecords. The class it’s self gets deleted so we can create this standalone record object. The OnlineStatusChangedMessage record represents a message used within a messaging system to convey changes in online status. Specifically, it encapsulates a boolean value indicating whether a user has transitioned to an online state or not.

Message Record

Subscribing View Model

Within a View Model like the Main View Model we add a reference in the constructor to the Messenger interface.

public class MainViewModel:ViewModelBase,IMainViewModel
{
    private readonly ILogger _logger;
    private readonly IMessenger _messenger;
    public MainWindow? MainWindow { get; } = null;
    public MainViewModel(
                            ILogger logger,
                            IMessenger messenger,
                            MainWindow mainWindow
                            )
    {
        logger.Information("Application Started.");

        _logger = logger;
        _messenger = messenger;
        MainWindow = mainWindow;
    }
}
namespace User_Controls.Messages
{
    public record OnlineStatusChangedMessage(bool isOnLine);
}
Updating the Property

We can then add a subscription and a method to handle the message.

public class MainViewModel:ViewModelBase,IMainViewModel
{
    private readonly ILogger _logger;
    private readonly IMessenger _messenger;
    public MainWindow? MainWindow { get; } = null;
    public MainViewModel(
                            ILogger logger,
                            IMessenger messenger,
                            MainWindow mainWindow
                            )
    {
        logger.Information("Application Started.");
        _logger = logger;
        _messenger = messenger;
        _messenger.Subscribe<OnlineStatusChangedMessage>(this, OnlineStatusChanged);
        MainWindow = mainWindow;
    }

    private void OnlineStatusChanged(object obj)
    {
        var message = (OnlineStatusChangedMessage)obj;
        SetOnlineStatus(message.isOnLine);
    }

    private bool _setOnlineStatus =false;
    public bool SetOnlineStatus(bool value)
    {
        _setOnlineStatus = value;
        return _setOnlineStatus;
    }
}

Sending the Message

From another view model we can now use the Message Record to send a message to the Main View Model that the Online Status has changed.

_messenger.Send(new OnlineStatusChangedMessage(true));

Conclusion

Implementing a custom messaging system in C# empowers developers to create highly interactive and responsive WPF applications. By leveraging the flexibility and control offered by a custom solution, developers can design messaging systems that seamlessly integrate with their applications, delivering enhanced user experiences and improved functionality.

With the insights provided in this article, you’re equipped to explore the possibilities of integrating a custom messaging system into your WPF applications, unlocking new avenues for real-time communication and interaction.

Example

App.xaml

<Application x:Class="TestApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TestApp"
             Startup="OnStartup">
    <Application.Resources>
         
    </Application.Resources>
</Application>

App.xaml.cs

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

using Serilog;

using System.IO;
using System.Text;
using System.Windows;
using TestApp.Services;
using TestApp.Services.ViewModels;
using TestApp.Stores;
using TestApp.ViewModels;

namespace TestApp
{
    public partial class App : Application
    {
        private MainWindow? _mainwindow;
        private ILogger? _logger;
        private IHost? _host;

        private void OnStartup(object sender, StartupEventArgs e)
        {
            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

            IConfigurationBuilder builder = new ConfigurationBuilder();
            BuildConfig(builder);

            _logger = new LoggerConfiguration()
                .ReadFrom.Configuration(builder.Build())
                .Enrich.FromLogContext()
                .CreateLogger();

            _logger.Information("App - OnStartup - Application Starting");
            _logger.Information("App - OnStartup - Adding Dependancies");

            _host = Host.CreateDefaultBuilder()
                .ConfigureServices((context, services) =>
                {
                    services.AddSingleton(_logger);
                    services.AddSingleton(m => new MainWindow());
                    services.AddSingleton(n => new NavigationStore());
                    services.AddSingleton<IMainViewModel, MainViewModel>();
                    services.AddSingleton<IMessengerService, MessengerService>();
                    services.AddSingleton<ILeftViewModel, LeftViewModel>();
                    services.AddSingleton<IRightViewModel, RightViewModel>();
                })
                .UseSerilog()
                .Build();

            _logger.Information("App - OnStartup - Creating the Main UI.");
            _mainwindow = _host.Services.GetRequiredService<MainWindow>();

            _logger.Information("App - OnStartup - Setting the UI DataContext.");
            _mainwindow.DataContext = ActivatorUtilities.CreateInstance<MainViewModel>(_host.Services);

            _logger.Information("App - OnStartup - Showing UI.");
            _mainwindow.Show();
        }

        private static void BuildConfig(IConfigurationBuilder builder)
        {
            builder.SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
                .AddEnvironmentVariables();
        }
    }
}

Commands Base

using System.Windows.Input;

namespace TestApp.Commands.Base
{
    public class CommandsBase : ICommand
    {
        private readonly Action<object?> _executeAction;
        private readonly Predicate<object?>? _canExecuteAction;

        public CommandsBase(Action<object?> executeAction)
        {
            _executeAction = executeAction;
            _canExecuteAction = null;
        }

        public CommandsBase(Action<object?> executeAction, Predicate<object?> canExecute)
        {
            _executeAction = executeAction;
            _canExecuteAction = canExecute;
        }

        public event EventHandler? CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object? parameter)
        {
            return _canExecuteAction == null ? true : _canExecuteAction(parameter);
        }
        public void Execute(object? parameter)
        {
            if (_executeAction != null)
            {
                _executeAction(parameter);
            }
        }
    }
}

View Model Base

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

namespace TestApp.ViewModels.Base
{
    public interface IViewModelBase
    {
        Window? parentWindow { get; set; }
        IViewModelBase? ParentViewModel { get; set; }
        event PropertyChangedEventHandler? PropertyChanged;
    }

    public abstract class ViewModelBase : INotifyPropertyChanged, IViewModelBase
    {
        public Window? parentWindow { get; set; } = null;
        public IViewModelBase? ParentViewModel { get; set; } = null;

        public event PropertyChangedEventHandler? PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName]string propertyName="")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public ViewModelBase() { }
        public ViewModelBase(IViewModelBase viewModelBase)
        {
            ParentViewModel = viewModelBase;
        }
    }
}

Message Records

using TestApp.ViewModels.Base;

namespace User_Controls.Messages
{
    public record LeftTextSentMessage(string text);
    public record RightTextSentMessage(string text);
    public record ActivateLeftView(ViewModelBase viewModel);
    public record ActivateRightView(ViewModelBase viewModel);
}

Navigation Store

using TestApp.ViewModels.Base;

namespace TestApp.Stores
{
    public interface INavigationStore
    {
        ViewModelBase? CurrentViewModel { get; set; }
        event Action? CurrentViewModelChanged;
    }

    public class NavigationStore : INavigationStore
    {
        public ViewModelBase? _CurrentViewModel;
        public ViewModelBase? CurrentViewModel
        {
            get => _CurrentViewModel;
            set
            {
                _CurrentViewModel = value;
                OnCurrentViewModelChanged();
            }
        }
        public event Action? CurrentViewModelChanged;

        private void OnCurrentViewModelChanged()
        {
            CurrentViewModelChanged?.Invoke();
        }
    }
}

Left View Model

using Serilog;
using System.Collections.ObjectModel;
using System.Windows.Input;
using TestApp.Commands.Base;
using TestApp.Services;
using TestApp.ViewModels.Base;
using User_Controls.Messages;

namespace TestApp.ViewModels
{
    public interface ILeftViewModel
    {
        ObservableCollection<string> TextList { get; }
        ICommand? Submit { get; }
        ICommand? Switch { get; }
        string UserText { get; set; }
    }
    public class LeftViewModel:ViewModelBase,ILeftViewModel
    {
        private readonly ILogger _logger;
        private readonly IMessengerService _messengerService;

        public LeftViewModel(
                        ILogger logger,
                        IMessengerService messengerService
                        )
        {
            logger.Information("Application Started.");

            _logger = logger;
            _messengerService = messengerService;
            _messengerService.Subscribe<LeftTextSentMessage>(this, LeftTextSentMessage);
        }

        private void LeftTextSentMessage(object obj)
        {
            var message = (LeftTextSentMessage)obj;
            TextList.Add(message.text);
        }

        private ObservableCollection<string> _textList = new ObservableCollection<string>();
        public ObservableCollection<string> TextList
        {
            get => _textList;
            set
            {
                _textList = value;
                OnPropertyChanged();
            }
        }
        
        private string _userText = string.Empty;
        public string UserText
        {
            get => _userText;
            set
            {
                _userText = value;
                OnPropertyChanged();
            }
        }

        public ICommand? Submit => new CommandsBase(SubmitCommandExecute);

        private void SubmitCommandExecute(object? obj)
        {
            _messengerService.Send(new RightTextSentMessage(UserText));
            UserText = string.Empty;
        }

        public ICommand? Switch => new CommandsBase(SwitchCommandExecute);

        private void SwitchCommandExecute(object? obj)
        {
            _messengerService.Send(new ActivateLeftView(this));
        }
    }
}

Right View Model

using Serilog;
using System.Collections.ObjectModel;
using System.Windows.Input;
using TestApp.Commands.Base;
using TestApp.Services;
using TestApp.ViewModels.Base;
using User_Controls.Messages;

namespace TestApp.ViewModels
{
    public interface IRightViewModel
    {
        ObservableCollection<string> TextList { get; }
        ICommand? Submit { get; }
        ICommand? Switch { get; }
        string UserText { get; set; }
    }
    public class RightViewModel:ViewModelBase,IRightViewModel
    {
        private readonly ILogger _logger;
        private readonly IMessengerService _messengerService;

        public RightViewModel(
                        ILogger logger,
                        IMessengerService messengerService
                        )
        {
            logger.Information("Application Started.");

            _logger = logger;
            _messengerService = messengerService;
            _messengerService.Subscribe<RightTextSentMessage>(this, RightTextSentMessage);
        }

        private void RightTextSentMessage(object obj)
        {
            var message = (RightTextSentMessage)obj;
            TextList.Add(message.text);
        }

        private ObservableCollection<string> _textList = new ObservableCollection<string>();
        public ObservableCollection<string> TextList
        {
            get => _textList;
            set
            {
                _textList = value;
                OnPropertyChanged();
            }
        }

        private string _userText = string.Empty;
        public string UserText
        {
            get => _userText;
            set
            {
                _userText = value;
                OnPropertyChanged();
            }
        }
                
        public ICommand? Submit => new CommandsBase(SubmitCommandExecute);
        private void SubmitCommandExecute(object? obj)
        {
            _messengerService.Send(new LeftTextSentMessage(UserText));
            UserText = string.Empty;
        }
        
        public ICommand? Switch => new CommandsBase(SwitchCommandExecute);

        private void SwitchCommandExecute(object? obj)
        {
            _messengerService.Send(new ActivateRightView(this));
        }
    }
}

Main View Model

using Serilog;
using TestApp.Stores;
using TestApp.ViewModels;
using TestApp.ViewModels.Base;
using User_Controls.Messages;

namespace TestApp.Services.ViewModels
{
    public interface IMainViewModel
    {
        IViewModelBase? CurrentViewModel { get; }
        MainWindow? MainWindow { get; }
        INavigationStore NavigationStore { get; }
        ILeftViewModel LeftViewModel { get; }
        IRightViewModel RightViewModel { get; }
    }

    public class MainViewModel : ViewModelBase, IMainViewModel
    {
        private readonly ILogger _logger;
        private readonly IMessengerService _messengerService;
        public MainWindow? MainWindow { get; } = null;

        public INavigationStore NavigationStore { get; }
        public IViewModelBase? CurrentViewModel => NavigationStore.CurrentViewModel;
        public ILeftViewModel LeftViewModel { get; }
        public IRightViewModel RightViewModel { get; }

        public MainViewModel(
                                ILogger logger,
                                IMessengerService messengerService,
                                ILeftViewModel leftViewModel,
                                IRightViewModel rightViewModel,
                                MainWindow mainWindow,
                                NavigationStore navigationStore
                                )
        {
            logger.Information("Application Started.");

            _logger = logger;
            _messengerService = messengerService;
            _messengerService.Subscribe<ActivateLeftView>(this, ChangeActiveView);
            _messengerService.Subscribe<ActivateRightView>(this, ChangeActiveView);

            LeftViewModel = leftViewModel;
            RightViewModel = rightViewModel;
            MainWindow = mainWindow;
            NavigationStore = navigationStore;

            NavigationStore.CurrentViewModel = (ViewModelBase)LeftViewModel;
            NavigationStore.CurrentViewModelChanged += OnCurrentViewModelChanged;
        }

        private void ChangeActiveView(object obj)
        {
            switch (obj)
            {
                case ActivateLeftView _:
                    NavigationStore.CurrentViewModel = (ViewModelBase)RightViewModel;
                    break;
                case ActivateRightView _:
                    NavigationStore.CurrentViewModel = (ViewModelBase)LeftViewModel;
                    break;
            }
        }

        private void OnCurrentViewModelChanged()
        {
            OnPropertyChanged(nameof(CurrentViewModel));
        }
    }
}

Left View

<UserControl x:Class="TestApp.Views.LeftView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:TestApp.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel
            Grid.Column="0"
            Orientation="Vertical">
            <Label
                Content="Left View"
                Foreground="White"
                FontSize="35"/>
            <Label
                Content="Enter text"
                Foreground="White"
                Margin="10 10 0 0"/>
            <TextBox 
                x:Name="txtBox" 
                HorizontalAlignment="Left" 
                VerticalAlignment="Top" 
                Height="23"
                Width="120"
                Margin="10,10,0,0" 
                TextWrapping="Wrap"
                Text="{Binding UserText}"/>
            <Button
                x:Name="btn"
                Content="Click me"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Width="100"
                Height="23"
                Margin="10,20,0,0"
                Command="{Binding Submit}"/>
            <Button
                x:Name="switch"
                Content="Change View"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Width="100"
                Height="23"
                Margin="10,20,0,0"
                Command="{Binding Switch}"/>
        </StackPanel>
        <ListView
            Grid.Column="1"
            x:Name="listView"
            HorizontalAlignment="Left"
            Height="400"
            Margin="10,10,0,0"
            VerticalAlignment="Top"
            Width="120"
            ItemsSource="{Binding TextList}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Text" DisplayMemberBinding="{Binding}" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</UserControl>

Right View

<UserControl x:Class="TestApp.Views.RightView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <StackPanel
            Grid.Column="0"
            Orientation="Vertical">
            <Label
                Content="Right View"
                Foreground="White"
                FontSize="35"/>
            <Label
                Content="Enter text"
                Foreground="White"
                Margin="10 10 0 0"/>
            <TextBox 
                x:Name="txtBox" 
                HorizontalAlignment="Left" 
                VerticalAlignment="Top" 
                Height="23"
                Width="120"
                Margin="10,10,0,0" 
                TextWrapping="Wrap"
                Text="{Binding UserText}"/>
            <Button
                x:Name="btn"
                Content="Click me"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Width="100"
                Height="23"
                Margin="10,20,0,0"
                Command="{Binding Submit}"/>
            <Button
                x:Name="switch"
                Content="Change View"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Width="100"
                Height="23"
                Margin="10,20,0,0"
                Command="{Binding Switch}"/>
        </StackPanel>
        <ListView
            Grid.Column="1"
            x:Name="listView"
            HorizontalAlignment="Left"
            Height="400"
            Margin="10,10,0,0"
            VerticalAlignment="Top"
            Width="120"
            ItemsSource="{Binding TextList}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Text" DisplayMemberBinding="{Binding}" />
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</UserControl>

appsettings.json (Content, Copy if Newer)

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console" ],
    "MinimumLevel": {
      "Default": "Debug",
      "Override": {
        "Default": "Information",
        "Microsoft": "Warning",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    },
    "WriteTo": [
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "(@l='Error' or @l='Fatal' or @l='Warning')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "C:\\CatiaWidgets\\Logs\\WarnErrFatal_.log",
                  "outputTemplate": "{Timestamp:o} [{Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 7
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "(@l='Information' or @l='Debug')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "C:\\CatiaWidgets\\Logs\\DebugInfo_.log",
                  "outputTemplate": "{Timestamp:o} [{Level:u3}] ({SourceContext}) {Message}{NewLine}{Exception}",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 7
                }
              }
            ]
          }
        }
      }
    ],
    "Enrich": [
      "FromLogContext",
      "WithMachineName"
    ],
    "Properties": {
      "Application": "MultipleLogFilesSample"
    }
  }
}

MainWindow.xaml

<Window x:Class="TestApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:views="clr-namespace:TestApp.Views"
        xmlns:viewmodels="clr-namespace:TestApp.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid 
        Grid.Column="1" 
        Grid.Row="1"
        Grid.RowSpan="2"
        Background="#151f2d"
        Margin="0 0 0 0">
        <Grid.Resources>
            <DataTemplate DataType="{x:Type viewmodels:LeftViewModel}">
                <views:LeftView/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type viewmodels:RightViewModel}">
                <views:RightView/>
            </DataTemplate>
        </Grid.Resources>
        <ContentControl Content="{Binding CurrentViewModel}"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Windows;

namespace TestApp
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}