< Summary

Information
Class: Amusoft.Toolkit.Mvvm.Core.NavigationService
Assembly: Amusoft.Toolkit.Mvvm.Core
File(s): /home/runner/work/Amusoft.Toolkit.Mvvm/Amusoft.Toolkit.Mvvm/src/Amusoft.Toolkit.Mvvm.Core/Navigation/NavigationService.cs
Tag: 8_14199367891
Line coverage
0%
Covered lines: 0
Uncovered lines: 99
Coverable lines: 99
Total lines: 208
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 46
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)0%2040%
add_OnHistoryChanged(...)100%210%
remove_OnHistoryChanged(...)100%210%
GoBackAsync()0%110100%
ClearHistory(...)0%620%
CanGoBack(...)0%620%
ClearView(...)0%620%
PushAsync()0%620%
TryGetRegionByName(...)0%620%
TryPushModelToView()0%620%
TryUpdateModelOfTheView()0%7280%
AddHistoryEntry(...)0%2040%
GetStrategies()0%2040%
PushAsync()0%2040%

File(s)

/home/runner/work/Amusoft.Toolkit.Mvvm/Amusoft.Toolkit.Mvvm/src/Amusoft.Toolkit.Mvvm.Core/Navigation/NavigationService.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Diagnostics.CodeAnalysis;
 5using System.Linq;
 6using System.Threading.Tasks;
 7using Amusoft.Toolkit.Mvvm.Core.Internals;
 8using Microsoft.Extensions.DependencyInjection;
 9using Microsoft.Extensions.Logging;
 10
 11namespace Amusoft.Toolkit.Mvvm.Core;
 12
 13internal class NavigationService : INavigationService
 14{
 15    private readonly IServiceProvider _serviceProvider;
 16    private readonly ILogger<NavigationService> _logger;
 17    private readonly IRestoreStrategyProvider[] _restoreStrategyProviders;
 18
 019    public NavigationService(IServiceProvider serviceProvider, IEnumerable<IRestoreStrategyProvider> strategyProviders, 
 20    {
 021        _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
 022        _logger = logger;
 023        _restoreStrategyProviders = strategyProviders.ToArray() ?? throw new ArgumentNullException(nameof(strategyProvid
 024    }
 25
 026    private readonly ConcurrentDictionary<string, Stack<NavigationModel>> _modelHistoryByRegionName = new();
 27
 028    private readonly WeakEvent<NavigationHistoryChanged> _onHistoryChanged = new();
 29    public event NavigationHistoryChanged OnHistoryChanged
 30    {
 031        add{ _onHistoryChanged.Add(value);}
 032        remove{ _onHistoryChanged.Remove(value);}
 33    }
 34
 35    public async Task<bool> GoBackAsync(string regionName)
 36    {
 37        try
 38        {
 039            if (!_modelHistoryByRegionName.TryGetValue(regionName, out var navStack))
 040                return false;
 041            if (!navStack.TryPop(out var navModel))
 042                return false;
 043            if (!navModel.TryGetModelInstance(out var model))
 044                return false;
 045            if (!RegionRegister.TryGetRegion(regionName, out var control))
 046                return false;
 047            if (!await TryUpdateModelOfTheView(model, control, false))
 048                return false;
 049            return true;
 50        }
 051        catch (Exception e)
 52        {
 053            _logger.LogError(e, "An error occured while trying to navigate back on the history of the region \"{Name}\".
 054            return false;
 55        }
 56        finally
 57        {
 058            _onHistoryChanged.Invoke(regionName);
 59        }
 060    }
 61
 62    public void ClearHistory(string regionName)
 63    {
 064        _logger.LogDebug("The history for region {Name} is being cleared.", regionName);
 065        if (!_modelHistoryByRegionName.TryGetValue(regionName, out var stack))
 066            return;
 67
 068        stack.Clear();
 069    }
 70
 71    public bool CanGoBack(string regionName)
 72    {
 073        if (!_modelHistoryByRegionName.TryGetValue(regionName, out var stack))
 074            return false;
 75
 076        return stack.Count > 0;
 77    }
 78
 79    public void ClearView(string regionName)
 80    {
 081        if (!RegionRegister.TryGetRegion(regionName, out var control))
 082            return;
 83
 084        control.Content = null;
 085    }
 86
 87    public async Task<bool> PushAsync<TModel>(string regionName, TModel model) where TModel : class
 88    {
 089        if (!TryGetRegionByName(regionName, out var control))
 090            return false;
 91
 092        return await TryPushModelToView(model, control);
 093    }
 94
 95    private bool TryGetRegionByName(string regionName, [NotNullWhen(true)] out IRegionControl? control)
 96    {
 097        if (!RegionRegister.TryGetRegion(regionName, out control))
 98        {
 099            _logger.LogError("The region \"{Name}\" is not registered.", regionName);
 0100            return false;
 101        }
 102
 0103        return true;
 104    }
 105
 106    private async Task<bool> TryPushModelToView<TModel>(TModel model, IRegionControl control) where TModel : class
 107    {
 108        try
 109        {
 0110            if (!await TryUpdateModelOfTheView(model, control, true))
 0111                return false;
 112
 0113            return true;
 114        }
 0115        catch (Exception e)
 116        {
 0117            _logger.LogError(e, "An error occured while trying to push the model to the view.");
 0118            return false;
 119        }
 0120    }
 121
 122    private async Task<bool> TryUpdateModelOfTheView<TModel>(TModel model, IRegionControl control, bool addOldToHistory)
 123    {
 124        try
 125        {
 0126            var oldModel = control.Content;
 0127            if (oldModel is INavigateFrom navFrom && !await navFrom.OnNavigatedFromAsync(new NavigatedFromContext(model)
 128            {
 0129                _logger.LogDebug("A navigation request from {From} to {To} was declined.", oldModel.GetType().Name, mode
 0130                return false;
 131            }
 132
 0133            if (addOldToHistory)
 0134                AddHistoryEntry(control.RegionName, oldModel);
 135
 0136            control.Content = model;
 137
 0138            if (model is INavigatedTo navigatedTo)
 0139                await navigatedTo.OnNavigatedToAsync(new NavigatedToContext(oldModel));
 0140            return true;
 141        }
 0142        catch (Exception e)
 143        {
 0144            _logger.LogError(e,
 0145                "An error occured while trying to update the Content with the model of \"{Name}\" to the view registered
 0146                typeof(TModel).Name,
 0147                control.RegionName
 0148            );
 0149            return false;
 150        }
 151        finally
 152        {
 0153            _onHistoryChanged.Invoke(control.RegionName);
 154        }
 0155    }
 156
 157    private void AddHistoryEntry(string regionName, object? oldModel)
 158    {
 0159        if (oldModel is not null)
 160        {
 0161            var stack = _modelHistoryByRegionName.GetOrAdd(regionName, _ => new());
 162            // var method =
 163
 164            IEnumerable<object> GetStrategies(IRestoreStrategyProvider provider)
 165            {
 0166                var method = provider.GetType().GetMethod(nameof(IRestoreStrategyProvider.GetStrategies));
 0167                if (method is null)
 0168                  return [];
 169
 0170                var specificMethod = method.MakeGenericMethod(oldModel.GetType());
 0171                var strategies = specificMethod.Invoke(provider, null);
 0172                return strategies as IEnumerable<object> ?? [];
 173            }
 174
 0175            var restoreStrategies = _restoreStrategyProviders
 0176                .SelectMany(GetStrategies)
 0177                .ToArray();
 178
 0179            var castedStrategies = Array.CreateInstance(typeof(IRestoreStrategy<>).MakeGenericType(oldModel.GetType()), 
 0180            Array.Copy(restoreStrategies, castedStrategies, restoreStrategies.Length);
 181
 0182            var modelType = typeof(NavigationModel<>).MakeGenericType(oldModel.GetType());
 0183            var instance = Activator.CreateInstance(modelType, args: [oldModel, castedStrategies]);
 0184            if (instance is not NavigationModel navigationModel)
 0185              throw new MvvmCoreException($"Navigation model could not be built for {modelType.Name}");
 0186            stack.Push(navigationModel);
 187        }
 0188    }
 189
 190    public async Task<bool> PushAsync<TModel>(string regionName, Action<TModel>? modification = null) where TModel : cla
 191    {
 192        try
 193        {
 0194            if (!TryGetRegionByName(regionName, out var control))
 0195                return false;
 196
 0197            var model = _serviceProvider.GetRequiredService<TModel>();
 0198            modification?.Invoke(model);
 199
 0200            return await TryPushModelToView(model, control);
 201        }
 0202        catch (Exception e)
 203        {
 0204            _logger.LogError(e, "An error occured while trying to push model \"{Name}\" to the view registered as region
 0205            return false;
 206        }
 0207    }
 208}