Skip to content

Commit 645f8a7

Browse files
committed
fix: log and surface view-model creation failures during navigation
1 parent 671d59b commit 645f8a7

2 files changed

Lines changed: 72 additions & 35 deletions

File tree

src/Uno.Extensions.Navigation.UI/Navigator.cs

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -53,49 +53,70 @@ protected Navigator(
5353
public async Task<NavigationResponse?> NavigateAsync(NavigationRequest request)
5454
{
5555
RouteUpdater.StartNavigation(this, Region, request);
56-
NavigationResponse? response;
56+
NavigationResponse? response = default;
57+
var isNavigationEntryPoint = request.Source is null;
5758

58-
if (request.Source is null)
59+
try
5960
{
60-
if (Logger.IsEnabled(LogLevel.Information)) Logger.LogInformationMessage($"Starting Navigation - Navigator: {this.GetType().Name} Request: {request.Route}");
61-
request = request with { Source = this };
62-
}
61+
if (request.Source is null)
62+
{
63+
if (Logger.IsEnabled(LogLevel.Information)) Logger.LogInformationMessage($"Starting Navigation - Navigator: {this.GetType().Name} Request: {request.Route}");
64+
request = request with { Source = this };
65+
}
6366

64-
if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebugMessage($" Navigator: {this.GetType().Name} Request: {request.Route}");
67+
if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebugMessage($" Navigator: {this.GetType().Name} Request: {request.Route}");
6568

66-
// Do any initialisation logic that may be
67-
// defined for the route - allows for
68-
// routes to be redirected
69-
request = InitializeRequest(request);
69+
// Do any initialisation logic that may be
70+
// defined for the route - allows for
71+
// routes to be redirected
72+
request = InitializeRequest(request);
7073

71-
// Redirect navigation if required
72-
// eg route that matches a child, should be routed to that child
73-
// eg route that doesn't match a page for frame nav should be sent to parent
74-
var redirection = await RedirectNavigateAsync(request);
75-
if (redirection is not null)
76-
{
77-
response = await redirection;
78-
}
79-
else
80-
{
81-
82-
// Append Internal qualifier to avoid requests being sent back to parent
83-
request = request.AsInternal();
84-
85-
if (request.Route.IsDialog())
74+
// Redirect navigation if required
75+
// eg route that matches a child, should be routed to that child
76+
// eg route that doesn't match a page for frame nav should be sent to parent
77+
var redirection = await RedirectNavigateAsync(request);
78+
if (redirection is not null)
8679
{
87-
// Dialogs will load a separate navigation hierarchy
88-
// so there's no need to route the request to child regions
89-
response = await DialogNavigateAsync(request);
80+
response = await redirection;
9081
}
9182
else
9283
{
93-
// Invoke the region specific navigation
94-
response = await RegionNavigateAsync(request);
84+
85+
// Append Internal qualifier to avoid requests being sent back to parent
86+
request = request.AsInternal();
87+
88+
if (request.Route.IsDialog())
89+
{
90+
// Dialogs will load a separate navigation hierarchy
91+
// so there's no need to route the request to child regions
92+
response = await DialogNavigateAsync(request);
93+
}
94+
else
95+
{
96+
// Invoke the region specific navigation
97+
response = await RegionNavigateAsync(request);
98+
}
99+
}
100+
return response;
101+
}
102+
catch (Exception ex)
103+
{
104+
// Log at the request's entry navigator only — nested navigators rethrow
105+
// through here too and would repeat the same exception at every level.
106+
// Log Route.Base (not the full Route) to keep Data/query values, which
107+
// can carry tokens or PII, out of Uno.Extensions.* log output.
108+
if (isNavigationEntryPoint && Logger.IsEnabled(LogLevel.Error))
109+
{
110+
Logger.LogErrorMessage(ex, $"Navigation failed for route '{request.Route.Base}'");
95111
}
112+
throw;
113+
}
114+
finally
115+
{
116+
// Must run even when navigation faults: skipping it leaks the notifier's
117+
// per-request tracking state and drops the RouteChanged notification.
118+
RouteUpdater.EndNavigation(this, Region, request, response);
96119
}
97-
RouteUpdater.EndNavigation(this, Region, request, response);
98-
return response;
99120
}
100121

101122
private async Task<Task<NavigationResponse?>?> RedirectNavigateAsync(NavigationRequest request)

src/Uno.Extensions.Navigation.UI/Navigators/ControlNavigator.cs

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,23 @@ protected virtual void UpdateRoute(Route? route)
422422

423423
services.AddScopedInstance(request);
424424

425-
var created = services.GetService(mapping!.ViewModel);
425+
object? created;
426+
try
427+
{
428+
created = services.GetService(mapping!.ViewModel);
429+
}
430+
catch (Exception ex)
431+
{
432+
// A view-model constructor (or one of its DI dependencies) throwing
433+
// here is app code failing, not a missing registration — don't fall
434+
// through to the reflection path, which would run the same failing
435+
// constructor again. Log before the fault propagates: no caller up
436+
// the navigation chain logs it, and at startup the faulted task is
437+
// typically unobserved, so this is the only diagnostic the app
438+
// author ever gets (see #3136).
439+
if (Logger.IsEnabled(LogLevel.Error)) Logger.LogErrorMessage(ex, $"Failed to create view model '{mapping!.ViewModel.Name}': the service provider threw while constructing it");
440+
throw;
441+
}
426442

427443
if (created is not null)
428444
{
@@ -437,9 +453,9 @@ protected virtual void UpdateRoute(Route? route)
437453
return ctr.Invoke(args);
438454
}
439455
}
440-
catch
456+
catch (Exception ex)
441457
{
442-
if (Logger.IsEnabled(LogLevel.Information)) Logger.LogInformationMessage("ViewModel not included in RouteMap, and unable to instance using Activator instead of ServiceProvider");
458+
if (Logger.IsEnabled(LogLevel.Error)) Logger.LogErrorMessage(ex, $"Failed to create view model '{mapping.ViewModel.Name}' via the reflection fallback (type isn't registered with the service provider)");
443459
}
444460
return default;
445461
});

0 commit comments

Comments
 (0)