From 268fcaaf3853470e2a56d0767a29929ca0bc62b8 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Mon, 27 Jan 2025 22:48:56 -0500 Subject: [PATCH 01/20] Update version.json --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 5647fb7..1f2f990 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "3.1.1", + "version": "3.2.0-beta.{height}", "assemblyVersion": { "precision": "revision" }, From b90d9363251e6a65f537d6ad0b1657df0007eeb0 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 13:46:26 -0500 Subject: [PATCH 02/20] Update MauiEventCollector.cs --- .../Infrastructure/MauiEventCollector.cs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Shiny.Mediator.Maui/Infrastructure/MauiEventCollector.cs b/src/Shiny.Mediator.Maui/Infrastructure/MauiEventCollector.cs index 8bce5d4..ba2f29f 100644 --- a/src/Shiny.Mediator.Maui/Infrastructure/MauiEventCollector.cs +++ b/src/Shiny.Mediator.Maui/Infrastructure/MauiEventCollector.cs @@ -6,31 +6,42 @@ public class MauiEventCollector : IEventCollector public IReadOnlyList> GetHandlers() where TEvent : IEvent { // I need to make this crawl the tree, but really I don't need a ton of use-cases here - var list = new List>(); - var mainPage = Application.Current?.MainPage; - // Application.Current.Windows - if (mainPage == null) - return list; + if (Application.Current == null) + return Array.Empty>(); + + if (Application.Current.Windows.Count == 0) + return Array.Empty>(); - if (mainPage is TabbedPage tabs) + var list = new List>(); + foreach (var window in Application.Current.Windows) + { + if (window.Page != null) + VisitPage(window.Page, list); + } + return list; + } + + + static void VisitPage(Page page, List> list) where TEvent : IEvent + { + if (page is TabbedPage tabs) { foreach (var tab in tabs.Children) { TryNavPage(tab, list); } } - else if (mainPage is FlyoutPage flyout) + else if (page is FlyoutPage flyout) { TryNavPage(flyout.Flyout, list); TryNavPage(flyout.Detail, list); // could be a tabs page } else { - TryNavPage(mainPage, list); + TryNavPage(page, list); } - return list; } - + static void TryNavPage(Page page, List> list) where TEvent : IEvent { @@ -69,6 +80,7 @@ static void TryAppendEvents(NavigationPage navPage, List(Shell shell, List> list) where TEvent : IEvent { From 9ca69b80b83fac0bc433025567ee477632e04712 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:26:27 -0500 Subject: [PATCH 03/20] Remove cancellation token from context to prevent misuse --- src/Shiny.Mediator/EventContext.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Shiny.Mediator/EventContext.cs b/src/Shiny.Mediator/EventContext.cs index 719081f..91daca3 100644 --- a/src/Shiny.Mediator/EventContext.cs +++ b/src/Shiny.Mediator/EventContext.cs @@ -5,8 +5,7 @@ public class EventContext : AbstractMediatorContext; public class EventContext( TEvent @event, - IEventHandler eventHandler, - CancellationToken cancellationToken + IEventHandler eventHandler ) : EventContext where TEvent : IEvent { public TEvent Event => @event; From d8f6e85284e2dae6be8d842d1d4cbb0d23d2c6c5 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:26:44 -0500 Subject: [PATCH 04/20] Rename RequestHandler to Handler in request context --- src/Shiny.Mediator/RequestContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shiny.Mediator/RequestContext.cs b/src/Shiny.Mediator/RequestContext.cs index 8f5abab..bef8e5f 100644 --- a/src/Shiny.Mediator/RequestContext.cs +++ b/src/Shiny.Mediator/RequestContext.cs @@ -2,7 +2,7 @@ namespace Shiny.Mediator; public class RequestContext(IRequestHandler handler) : AbstractMediatorContext { - public IRequestHandler RequestHandler => handler; + public IRequestHandler Handler => handler; } public class RequestContext( From 860d4a53bf27cb9ed1197ef1379276caf4f62d1d Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:27:24 -0500 Subject: [PATCH 05/20] Minor csproj cleanup --- src/Shiny.Mediator.Maui/Shiny.Mediator.Maui.csproj | 2 ++ src/Shiny.Mediator/Shiny.Mediator.csproj | 2 ++ tests/Shiny.Mediator.Tests/ConfigurationTests.cs | 4 +--- tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj | 3 --- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Shiny.Mediator.Maui/Shiny.Mediator.Maui.csproj b/src/Shiny.Mediator.Maui/Shiny.Mediator.Maui.csproj index 1d6de1e..29b3c8a 100644 --- a/src/Shiny.Mediator.Maui/Shiny.Mediator.Maui.csproj +++ b/src/Shiny.Mediator.Maui/Shiny.Mediator.Maui.csproj @@ -5,6 +5,8 @@ enable enable Shiny.Mediator + + diff --git a/src/Shiny.Mediator/Shiny.Mediator.csproj b/src/Shiny.Mediator/Shiny.Mediator.csproj index 8e2d1de..4f468df 100644 --- a/src/Shiny.Mediator/Shiny.Mediator.csproj +++ b/src/Shiny.Mediator/Shiny.Mediator.csproj @@ -5,6 +5,8 @@ enable enable The main Shiny Mediator library where all the infrastructure lives + + diff --git a/tests/Shiny.Mediator.Tests/ConfigurationTests.cs b/tests/Shiny.Mediator.Tests/ConfigurationTests.cs index 9fa9192..0b4df02 100644 --- a/tests/Shiny.Mediator.Tests/ConfigurationTests.cs +++ b/tests/Shiny.Mediator.Tests/ConfigurationTests.cs @@ -1,11 +1,9 @@ using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.Memory; -using Xunit.Abstractions; namespace Shiny.Mediator.Tests; -public class ConfigurationTests(ITestOutputHelper output) +public class ConfigurationTests { [Fact] public void AddConfiguration() diff --git a/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj b/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj index 6d864fd..eb4ede9 100644 --- a/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj +++ b/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj @@ -33,9 +33,6 @@ - - - From e936bab288526acabf10fc1f27600a78f7e348a7 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:27:39 -0500 Subject: [PATCH 06/20] MAUI AlertDialogService now respects windows --- .../Infrastructure/AlertDialogService.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/Shiny.Mediator.Maui/Infrastructure/AlertDialogService.cs b/src/Shiny.Mediator.Maui/Infrastructure/AlertDialogService.cs index e56c549..583fe97 100644 --- a/src/Shiny.Mediator.Maui/Infrastructure/AlertDialogService.cs +++ b/src/Shiny.Mediator.Maui/Infrastructure/AlertDialogService.cs @@ -1,8 +1,22 @@ +using Microsoft.Extensions.Logging; + namespace Shiny.Mediator.Infrastructure; -public class AlertDialogService : IAlertDialogService +public class AlertDialogService(ILogger logger) : IAlertDialogService { public void Display(string title, string message) - => Application.Current.MainPage.DisplayAlert(title, message, "OK"); + { + var app = Application.Current; + if (app == null) + return; + + var window = app.Windows.FirstOrDefault(); + if (window?.Page == null) + return; + + window.Page + .DisplayAlert(title, message, "OK") + .RunInBackground(logger); + } } \ No newline at end of file From 7dd095b1debf9364ced24135227e9d0d47f009cc Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:28:12 -0500 Subject: [PATCH 07/20] Minor cleanup --- .../Infrastructure/BlazorEventCollector.cs | 2 +- src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs | 2 +- .../Middleware/MainThreadRequestMiddleware.cs | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Shiny.Mediator.Blazor/Infrastructure/BlazorEventCollector.cs b/src/Shiny.Mediator.Blazor/Infrastructure/BlazorEventCollector.cs index f2ca27b..98aa03a 100644 --- a/src/Shiny.Mediator.Blazor/Infrastructure/BlazorEventCollector.cs +++ b/src/Shiny.Mediator.Blazor/Infrastructure/BlazorEventCollector.cs @@ -39,7 +39,7 @@ public IReadOnlyList> GetHandlers() where TEvent : public IComponent CreateInstance(Type componentType) { - var component = (IComponent)Activator.CreateInstance(componentType); + var component = (IComponent)Activator.CreateInstance(componentType)!; var isHandler = componentType .GetInterfaces() .Any(x => diff --git a/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs b/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs index cfeebcb..0976b8e 100644 --- a/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs +++ b/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs @@ -6,7 +6,7 @@ namespace Shiny.Mediator.Blazor.Infrastructure; public class InternetService(IJSRuntime jsruntime) : IInternetService, IDisposable { - public event EventHandler StateChanged; + public event EventHandler? StateChanged; public bool IsAvailable => ((IJSInProcessRuntime)jsruntime).Invoke("MediatorServices.isOnline"); diff --git a/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestMiddleware.cs b/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestMiddleware.cs index fdd2bd4..63e7085 100644 --- a/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestMiddleware.cs +++ b/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestMiddleware.cs @@ -13,9 +13,7 @@ public Task Process( CancellationToken cancellationToken ) { - var attr = context.RequestHandler.GetHandlerHandleMethodAttribute(); - TResult result = default!; - + var attr = context.Handler.GetHandlerHandleMethodAttribute(); if (attr == null) return next(); From 61930e5b3b228949b83e32e7ce68ec8a3ae701d7 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:28:37 -0500 Subject: [PATCH 08/20] Update Shiny.Mediator.Contracts.csproj --- src/Shiny.Mediator.Contracts/Shiny.Mediator.Contracts.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Shiny.Mediator.Contracts/Shiny.Mediator.Contracts.csproj b/src/Shiny.Mediator.Contracts/Shiny.Mediator.Contracts.csproj index 0d087a8..a7e0877 100644 --- a/src/Shiny.Mediator.Contracts/Shiny.Mediator.Contracts.csproj +++ b/src/Shiny.Mediator.Contracts/Shiny.Mediator.Contracts.csproj @@ -4,5 +4,7 @@ enable enable Shiny.Mediator + + From 6ea01630e434c8239ba31953a8b13acf9c6fdd20 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:28:45 -0500 Subject: [PATCH 09/20] Update StorageService.cs --- src/Shiny.Mediator.Maui/Infrastructure/StorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Shiny.Mediator.Maui/Infrastructure/StorageService.cs b/src/Shiny.Mediator.Maui/Infrastructure/StorageService.cs index 9455e8b..f23a698 100644 --- a/src/Shiny.Mediator.Maui/Infrastructure/StorageService.cs +++ b/src/Shiny.Mediator.Maui/Infrastructure/StorageService.cs @@ -13,7 +13,7 @@ public Task Set(string key, T value) } - public Task Get(string key) + public Task Get(string key) { T? returnValue = default; var path = this.GetFilePath(key); From 111bb155a3c0f4d72b547fb60cbfdcdc3ab84058 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:29:05 -0500 Subject: [PATCH 10/20] Handler rename --- .../Middleware/OfflineAvailableRequestMiddleware.cs | 2 +- .../Middleware/ReplayStreamMiddleware.cs | 2 +- .../Middleware/UserErrorNotificationsRequestMiddleware.cs | 2 +- .../Handlers/ResilientRequestMiddleware.cs | 4 ++-- .../Caching/Infrastructure/CachingRequestMiddleware.cs | 6 +++--- .../Middleware/PerformanceLoggingRequestMiddleware.cs | 2 +- .../Middleware/TimerRefreshStreamRequestMiddleware.cs | 4 ++-- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs b/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs index 2193e63..f944f2f 100644 --- a/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs +++ b/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs @@ -18,7 +18,7 @@ public async Task Process( CancellationToken cancellationToken ) { - if (!this.IsEnabled(context.RequestHandler, context.Request)) + if (!this.IsEnabled(context.Handler, context.Request)) return await next().ConfigureAwait(false); var result = default(TResult); diff --git a/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs b/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs index 0ddc1ca..b06a724 100644 --- a/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs +++ b/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs @@ -26,7 +26,7 @@ public IAsyncEnumerable Process( CancellationToken cancellationToken ) { - if (!this.IsEnabled(context.Request, context.RequestHandler)) + if (!this.IsEnabled(context.Request, context.Handler)) return next(); logger.LogDebug("Enabled - {Request}", context.Request); diff --git a/src/Shiny.Mediator.AppSupport/Middleware/UserErrorNotificationsRequestMiddleware.cs b/src/Shiny.Mediator.AppSupport/Middleware/UserErrorNotificationsRequestMiddleware.cs index 5ce3072..b38220d 100644 --- a/src/Shiny.Mediator.AppSupport/Middleware/UserErrorNotificationsRequestMiddleware.cs +++ b/src/Shiny.Mediator.AppSupport/Middleware/UserErrorNotificationsRequestMiddleware.cs @@ -18,7 +18,7 @@ public async Task Process( CancellationToken cancellationToken ) { - var section = configuration.GetHandlerSection("UserErrorNotifications", context.Request!, context.RequestHandler); + var section = configuration.GetHandlerSection("UserErrorNotifications", context.Request!, context.Handler); if (section == null) return await next().ConfigureAwait(false); diff --git a/src/Shiny.Mediator.Resilience/Handlers/ResilientRequestMiddleware.cs b/src/Shiny.Mediator.Resilience/Handlers/ResilientRequestMiddleware.cs index a03e1ba..6c9ee3c 100644 --- a/src/Shiny.Mediator.Resilience/Handlers/ResilientRequestMiddleware.cs +++ b/src/Shiny.Mediator.Resilience/Handlers/ResilientRequestMiddleware.cs @@ -19,7 +19,7 @@ CancellationToken cancellationToken ) { ResiliencePipeline? pipeline = null; - var section = configuration.GetHandlerSection("Resilience", context.Request!, context.RequestHandler); + var section = configuration.GetHandlerSection("Resilience", context.Request!, context.Handler); if (section != null) { @@ -27,7 +27,7 @@ CancellationToken cancellationToken } else { - var attribute = context.RequestHandler.GetHandlerHandleMethodAttribute(); + var attribute = context.Handler.GetHandlerHandleMethodAttribute(); if (attribute != null) pipeline = pipelineProvider.GetPipeline(attribute.ConfigurationKey.ToLower()); } diff --git a/src/Shiny.Mediator/Caching/Infrastructure/CachingRequestMiddleware.cs b/src/Shiny.Mediator/Caching/Infrastructure/CachingRequestMiddleware.cs index 42c5ed9..95b049d 100644 --- a/src/Shiny.Mediator/Caching/Infrastructure/CachingRequestMiddleware.cs +++ b/src/Shiny.Mediator/Caching/Infrastructure/CachingRequestMiddleware.cs @@ -19,11 +19,11 @@ CancellationToken cancellationToken { CacheAttribute? attribute = null; var cacheKey = ContractUtils.GetObjectKey(context.Request!); - var section = configuration.GetHandlerSection("Cache", context.Request!, context.RequestHandler); + var section = configuration.GetHandlerSection("Cache", context.Request!, context.Handler); if (section == null) { - attribute = context.RequestHandler.GetHandlerHandleMethodAttribute(); + attribute = context.Handler.GetHandlerHandleMethodAttribute(); } else { @@ -65,7 +65,7 @@ CancellationToken cancellationToken .ConfigureAwait(false)!; logger.LogDebug("Cache Hit: {Hit} - {Request} - Key: {RequestKey}", hit, context.Request, cacheKey); - context.Cache(new CacheContext(cacheKey, hit, entry.CreatedAt)); + context.Cache(new CacheContext(cacheKey, hit, entry!.CreatedAt)); } return result!; } diff --git a/src/Shiny.Mediator/Middleware/PerformanceLoggingRequestMiddleware.cs b/src/Shiny.Mediator/Middleware/PerformanceLoggingRequestMiddleware.cs index 67e713a..3019815 100644 --- a/src/Shiny.Mediator/Middleware/PerformanceLoggingRequestMiddleware.cs +++ b/src/Shiny.Mediator/Middleware/PerformanceLoggingRequestMiddleware.cs @@ -17,7 +17,7 @@ public async Task Process( CancellationToken cancellationToken ) { - var section = configuration.GetHandlerSection("PerformanceLogging", context.Request!, context.RequestHandler); + var section = configuration.GetHandlerSection("PerformanceLogging", context.Request!, context.Handler); if (section == null) return await next().ConfigureAwait(false); diff --git a/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs b/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs index b13678d..76bd6b9 100644 --- a/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs +++ b/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs @@ -24,14 +24,14 @@ CancellationToken cancellationToken } else { - var section = configuration.GetHandlerSection("TimerRefresh", context.Request, context.RequestHandler); + var section = configuration.GetHandlerSection("TimerRefresh", context.Request, context.Handler); if (section != null) { interval = section.GetValue("IntervalSeconds", 0); } else { - var attribute = context.RequestHandler.GetHandlerHandleMethodAttribute(); + var attribute = context.Handler.GetHandlerHandleMethodAttribute(); if (attribute != null) interval = attribute.IntervalSeconds; } From 4a06225e3d00b755513af23522ad70fa01b1dc0a Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:29:24 -0500 Subject: [PATCH 11/20] [Fix] ensure initial headers are passed to event context --- .../Infrastructure/Impl/Mediator_Events.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Events.cs b/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Events.cs index 3dc56e9..e52da24 100644 --- a/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Events.cs +++ b/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Events.cs @@ -15,15 +15,9 @@ public virtual async Task> Publish( params IEnumerable<(string Key, object Value)> headers ) where TEvent : IEvent { - // allow registered services to be transient/scoped/singleton using var scope = services.CreateScope(); var handlers = scope.ServiceProvider.GetServices>().ToList(); - - // "covariance" event publishing chain - // typeof(TEvent).BaseType == typeof(object) // loop on basetype until object - //typeof(TEvent).BaseType.GetInterfaces().Any(x => x == typeof(IEvent)); // must be implementing IEvent although it wouldn't have been able to compile anyhow - //var globalHandlers = scope.ServiceProvider.GetServices>().ToList(); // global handlers - + AppendHandlersIf(handlers, this.subscriptions); foreach (var collector in collectors) AppendHandlersIf(handlers, collector); @@ -43,6 +37,7 @@ public virtual async Task> Publish( .PublishCore( @event, handler, + headers, logger, middlewares, cancellationToken @@ -79,13 +74,14 @@ public IDisposable Subscribe(Func, Cancella async Task> PublishCore( TEvent @event, IEventHandler eventHandler, + IEnumerable<(string Key, object Value)> headers, ILogger logger, IEnumerable> middlewares, CancellationToken cancellationToken ) where TEvent : IEvent { - var context = new EventContext(@event, eventHandler, cancellationToken); - // TODO: populate headers + var context = new EventContext(@event, eventHandler); + context.PopulateHeaders(headers); var handlerDelegate = new EventHandlerDelegate(() => { From d0cbd679cdc96952fe3805d38e3e6fa1ac9eea51 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:34:59 -0500 Subject: [PATCH 12/20] Minor handler execution improvements --- src/Shiny.Mediator/Infrastructure/Impl/Mediator_Requests.cs | 5 +++-- src/Shiny.Mediator/Infrastructure/Impl/Mediator_Streams.cs | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Requests.cs b/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Requests.cs index 84b1cf4..2f3593f 100644 --- a/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Requests.cs +++ b/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Requests.cs @@ -14,8 +14,9 @@ public virtual async Task> RequestWithContext( { using var scope = services.CreateScope(); var wrapperType = typeof(RequestResultWrapper<,>).MakeGenericType([request.GetType(), typeof(TResult)]); - var wrapper = (IRequestResultWrapper)Activator.CreateInstance( - wrapperType, + var wrapper = (IRequestResultWrapper)ActivatorUtilities.CreateInstance( + scope.ServiceProvider, + wrapperType, [scope.ServiceProvider, request, headers, cancellationToken] ); var execution = await wrapper.Handle().ConfigureAwait(false); diff --git a/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Streams.cs b/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Streams.cs index e98227e..79d751f 100644 --- a/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Streams.cs +++ b/src/Shiny.Mediator/Infrastructure/Impl/Mediator_Streams.cs @@ -14,8 +14,10 @@ public virtual RequestResult> RequestWithContext).MakeGenericType([request.GetType(), typeof(TResult)]); - var wrapper = (IStreamRequestWrapper)Activator.CreateInstance( - wrapperType, + + var wrapper = (IStreamRequestWrapper)ActivatorUtilities.CreateInstance( + scope.ServiceProvider, + wrapperType, [scope.ServiceProvider, request, headers, cancellationToken] ); var execution = wrapper.Handle(); From e90f1f78b3d58b12ac9b3819d18d6592ee1c69c3 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:43:47 -0500 Subject: [PATCH 13/20] Sample fix --- samples/Sample/appsettings.json | 2 +- .../Middleware/OfflineAvailableRequestMiddleware.cs | 2 +- src/Shiny.Mediator.AppSupport/Shiny.Mediator.AppSupport.csproj | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/samples/Sample/appsettings.json b/samples/Sample/appsettings.json index a6c4c8a..e51069a 100644 --- a/samples/Sample/appsettings.json +++ b/samples/Sample/appsettings.json @@ -45,7 +45,7 @@ }, "UserErrorNotifications": { // this works - "Sample.Handlers.ErrorRequestHandler": { + "Sample.Handlers.ErrorCommandHandler": { "*": { "Title": "ERROR", "Message" : "Failed to do something" diff --git a/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs b/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs index f944f2f..52e8c58 100644 --- a/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs +++ b/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs @@ -43,7 +43,7 @@ CancellationToken cancellationToken } - async Task GetOffline(RequestContext context) + async Task GetOffline(RequestContext context) { TResult result = default; var offlineResult = await offline.Get(context.Request!); diff --git a/src/Shiny.Mediator.AppSupport/Shiny.Mediator.AppSupport.csproj b/src/Shiny.Mediator.AppSupport/Shiny.Mediator.AppSupport.csproj index 7b26cb7..9ac7b9e 100644 --- a/src/Shiny.Mediator.AppSupport/Shiny.Mediator.AppSupport.csproj +++ b/src/Shiny.Mediator.AppSupport/Shiny.Mediator.AppSupport.csproj @@ -5,6 +5,7 @@ enable enable Shiny.Mediator + From 1127b5043afe4b97faa6e029e45c7e5545473ac4 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:57:50 -0500 Subject: [PATCH 14/20] Fix sample --- samples/Sample/MauiProgram.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/samples/Sample/MauiProgram.cs b/samples/Sample/MauiProgram.cs index 84e5b9e..5352981 100644 --- a/samples/Sample/MauiProgram.cs +++ b/samples/Sample/MauiProgram.cs @@ -49,13 +49,17 @@ public static MauiApp CreateMauiApp() builder.Services.AddShinyMediator(x => x .UseMaui() .UseBlazor() - .AddPersistentCache() + + // Validation - you can only have both, but don't .AddDataAnnotations() + // .AddFluentValidation() + .AddMauiHttpDecorator() .AddPrismSupport() - // .AddFluentValidation() // don't add both .AddResiliencyMiddleware(builder.Configuration) + // Cache - you can only have one + // .AddPersistentCache() .AddMemoryCaching(y => { y.ExpirationScanFrequency = TimeSpan.FromSeconds(5); From cc075516ffe60057ae6fa33d0be2651ee7b73b1f Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 19:58:01 -0500 Subject: [PATCH 15/20] Ensure only one cache can be registered --- src/Shiny.Mediator/Caching/CacheExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Shiny.Mediator/Caching/CacheExtensions.cs b/src/Shiny.Mediator/Caching/CacheExtensions.cs index b720640..7b24b8e 100644 --- a/src/Shiny.Mediator/Caching/CacheExtensions.cs +++ b/src/Shiny.Mediator/Caching/CacheExtensions.cs @@ -9,6 +9,9 @@ public static class CacheExtensions { public static ShinyConfigurator AddCaching(this ShinyConfigurator cfg) where TCache : class, ICacheService { + if (cfg.Services.Any(x => x.ServiceType == typeof(ICacheService))) + throw new InvalidOperationException("You can only have one mediator cache service registered"); + cfg.Services.AddSingletonAsImplementedInterfaces(); cfg.Services.AddSingletonAsImplementedInterfaces(); cfg.AddOpenRequestMiddleware(typeof(CachingRequestMiddleware<,>)); From 9c8db383932b908208dc0c2f43e6b6ca81ef15b2 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 20:14:11 -0500 Subject: [PATCH 16/20] Improved safetying to MAUI registration and http decorator --- .../Http/MauiHttpRequestDecorator.cs | 19 +++++--- src/Shiny.Mediator.Maui/MauiExtensions.cs | 43 +++++++++++-------- src/Shiny.Mediator/ShinyConfigurator.cs | 4 +- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/Shiny.Mediator.Maui/Http/MauiHttpRequestDecorator.cs b/src/Shiny.Mediator.Maui/Http/MauiHttpRequestDecorator.cs index 5e1017d..ae85287 100644 --- a/src/Shiny.Mediator.Maui/Http/MauiHttpRequestDecorator.cs +++ b/src/Shiny.Mediator.Maui/Http/MauiHttpRequestDecorator.cs @@ -1,12 +1,13 @@ using System.Globalization; using System.Net.Http.Headers; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; namespace Shiny.Mediator.Http; public class MauiHttpRequestDecorator( - IConfiguration configuration, + ILogger> logger, IAppInfo appInfo, IDeviceInfo deviceInfo, IGeolocation geolocation @@ -22,11 +23,19 @@ public async Task Decorate(HttpRequestMessage httpMessage, TRequest request) httpMessage.Headers.Add("DeviceVersion", deviceInfo.Version.ToString()); httpMessage.Headers.AcceptLanguage.Add(new StringWithQualityHeaderValue(CultureInfo.CurrentCulture.Name)); - if (configuration["Mediator:Http:GpsHeader"] == "true") + try { - var gps = await geolocation.GetLastKnownLocationAsync(); - if (gps != null) - httpMessage.Headers.Add("GpsCoords", $"{gps.Latitude},{gps.Longitude}"); + var result = await Permissions.CheckStatusAsync(); + if (result == PermissionStatus.Granted) + { + var gps = await geolocation.GetLastKnownLocationAsync(); + if (gps != null) + httpMessage.Headers.Add("GpsCoords", $"{gps.Latitude},{gps.Longitude}"); + } + } + catch (Exception ex) + { + logger.LogInformation(ex, "Failed to get GPS"); } } } \ No newline at end of file diff --git a/src/Shiny.Mediator.Maui/MauiExtensions.cs b/src/Shiny.Mediator.Maui/MauiExtensions.cs index 5143076..33dda99 100644 --- a/src/Shiny.Mediator.Maui/MauiExtensions.cs +++ b/src/Shiny.Mediator.Maui/MauiExtensions.cs @@ -30,6 +30,7 @@ public static MauiAppBuilder AddShinyMediator( return builder; } + /// /// Adds Maui Event Collector to mediator /// @@ -39,14 +40,10 @@ public static MauiAppBuilder AddShinyMediator( public static ShinyConfigurator UseMaui(this ShinyConfigurator cfg, bool includeStandardMiddleware = true) { cfg.AddEventCollector(); - cfg.Services.TryAddSingleton(); - cfg.Services.TryAddSingleton(); - cfg.Services.TryAddSingleton(); - cfg.Services.TryAddSingleton(FileSystem.Current); - cfg.Services.TryAddSingleton(Connectivity.Current); if (includeStandardMiddleware) { + cfg.AddMauiInfrastructure(); cfg.AddMainThreadMiddleware(); cfg.AddStandardAppSupportMiddleware(); } @@ -54,25 +51,35 @@ public static ShinyConfigurator UseMaui(this ShinyConfigurator cfg, bool include } + /// + /// Ensures all necessary MAUI services are installed for middleware + /// + /// + /// + public static ShinyConfigurator AddMauiInfrastructure(this ShinyConfigurator cfg) + { + cfg.Services.TryAddSingleton(); + cfg.Services.TryAddSingleton(); + cfg.Services.TryAddSingleton(); + cfg.Services.TryAddSingleton(FileSystem.Current); + cfg.Services.TryAddSingleton(AppInfo.Current); + cfg.Services.TryAddSingleton(DeviceDisplay.Current); + cfg.Services.TryAddSingleton(DeviceInfo.Current); + cfg.Services.TryAddSingleton(Geolocation.Default); + cfg.Services.TryAddSingleton(Connectivity.Current); + return cfg; + } + + /// /// This appends app version, device info, and culture to the HTTP request handling framework /// /// - /// /// - public static ShinyConfigurator AddMauiHttpDecorator(this ShinyConfigurator configurator, bool appendGpsHeader = false) + public static ShinyConfigurator AddMauiHttpDecorator(this ShinyConfigurator configurator) { - configurator.Services.TryAddSingleton(AppInfo.Current); - configurator.Services.TryAddSingleton(DeviceDisplay.Current); - configurator.Services.TryAddSingleton(DeviceInfo.Current); - configurator.Services.TryAddSingleton(Geolocation.Default); - - configurator.Services.Add(new ServiceDescriptor( - typeof(IHttpRequestDecorator<,>), - null, - typeof(MauiHttpRequestDecorator<,>), - ServiceLifetime.Singleton - )); + configurator.AddMauiInfrastructure(); + configurator.Services.AddSingleton(typeof(IHttpRequestDecorator<,>), typeof(MauiHttpRequestDecorator<,>)); return configurator; } diff --git a/src/Shiny.Mediator/ShinyConfigurator.cs b/src/Shiny.Mediator/ShinyConfigurator.cs index 8d6806e..e4eb6a9 100644 --- a/src/Shiny.Mediator/ShinyConfigurator.cs +++ b/src/Shiny.Mediator/ShinyConfigurator.cs @@ -71,7 +71,9 @@ public ShinyConfigurator AddOpenEventMiddleware(Type implementationType, Service public ShinyConfigurator AddEventCollector() where TImpl : class, IEventCollector { - services.AddSingleton(); + if (!services.Any(x => x.ServiceType == typeof(TImpl))) + services.AddSingleton(); + return this; } } \ No newline at end of file From e7cabf998007f66d140d66f28288122ae2bbfcd0 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 20:21:33 -0500 Subject: [PATCH 17/20] Test fixes --- tests/Shiny.Mediator.Tests/DapperTests.cs | 2 -- tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Shiny.Mediator.Tests/DapperTests.cs b/tests/Shiny.Mediator.Tests/DapperTests.cs index b7ffee7..d444dd8 100644 --- a/tests/Shiny.Mediator.Tests/DapperTests.cs +++ b/tests/Shiny.Mediator.Tests/DapperTests.cs @@ -1,10 +1,8 @@ -using System.ComponentModel; using Dapper; using DryIoc.Microsoft.DependencyInjection; using Microsoft.Data.Sqlite; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Npgsql; using Xunit.Abstractions; namespace Shiny.Mediator.Tests; diff --git a/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj b/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj index eb4ede9..32b4c1f 100644 --- a/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj +++ b/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj @@ -17,6 +17,7 @@ + From 0e37dfd459d4b0707cfa035e352d344b56eda196 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 20:26:10 -0500 Subject: [PATCH 18/20] Improve persistent cache registration to ensure it has all necessary infra --- .../AppSupportExtensions.cs | 9 ------ src/Shiny.Mediator.Blazor/BlazorExtensions.cs | 28 ++++++++++++++++--- src/Shiny.Mediator.Maui/MauiExtensions.cs | 16 +++++++++-- 3 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/Shiny.Mediator.AppSupport/AppSupportExtensions.cs b/src/Shiny.Mediator.AppSupport/AppSupportExtensions.cs index 5b5d773..049c470 100644 --- a/src/Shiny.Mediator.AppSupport/AppSupportExtensions.cs +++ b/src/Shiny.Mediator.AppSupport/AppSupportExtensions.cs @@ -7,15 +7,6 @@ namespace Shiny.Mediator; public static class AppSupportExtensions { - /// - /// Adds a file based caching service - ideal for cache surviving across app sessions - /// - /// - /// - public static ShinyConfigurator AddPersistentCache(this ShinyConfigurator configurator) - => configurator.AddCaching(); - - /// /// Adds standard app support middleware - offline, replay stream, & user notification /// diff --git a/src/Shiny.Mediator.Blazor/BlazorExtensions.cs b/src/Shiny.Mediator.Blazor/BlazorExtensions.cs index 6ba532b..67d37e7 100644 --- a/src/Shiny.Mediator.Blazor/BlazorExtensions.cs +++ b/src/Shiny.Mediator.Blazor/BlazorExtensions.cs @@ -43,13 +43,33 @@ public static ShinyConfigurator UseBlazor(this ShinyConfigurator cfg, bool inclu cfg.Services.AddSingletonAsImplementedInterfaces(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Create("Browser"))) { - cfg.Services.TryAddSingleton(); - cfg.Services.TryAddSingleton(); - cfg.Services.TryAddSingleton(); - + cfg.AddBlazorInfrastructure(); if (includeStandardMiddleware) cfg.AddStandardAppSupportMiddleware(); } return cfg; } + + + public static ShinyConfigurator AddBlazorInfrastructure(this ShinyConfigurator cfg) + { + cfg.Services.TryAddSingleton(); + cfg.Services.TryAddSingleton(); + cfg.Services.TryAddSingleton(); + + return cfg; + } + + + /// + /// Adds a file based caching service - ideal for cache surviving across app sessions + /// + /// + /// + public static ShinyConfigurator AddBlazorPersistentCache(this ShinyConfigurator configurator) + { + configurator.AddBlazorInfrastructure(); + configurator.AddCaching(); + return configurator; + } } \ No newline at end of file diff --git a/src/Shiny.Mediator.Maui/MauiExtensions.cs b/src/Shiny.Mediator.Maui/MauiExtensions.cs index 33dda99..2615b3f 100644 --- a/src/Shiny.Mediator.Maui/MauiExtensions.cs +++ b/src/Shiny.Mediator.Maui/MauiExtensions.cs @@ -29,8 +29,20 @@ public static MauiAppBuilder AddShinyMediator( }); return builder; } - - + + + /// + /// Adds a file based caching service - ideal for cache surviving across app sessions + /// + /// + /// + public static ShinyConfigurator AddMauiPersistentCache(this ShinyConfigurator configurator) + { + configurator.AddMauiInfrastructure(); + configurator.AddCaching(); + return configurator; + } + /// /// Adds Maui Event Collector to mediator /// From 132d8f1be00053fc33f962508b780358bab02a0a Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 20:26:18 -0500 Subject: [PATCH 19/20] cleanup --- samples/Sample/Handlers/SingletonEventHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/Sample/Handlers/SingletonEventHandler.cs b/samples/Sample/Handlers/SingletonEventHandler.cs index 4d3ddaa..ab1203a 100644 --- a/samples/Sample/Handlers/SingletonEventHandler.cs +++ b/samples/Sample/Handlers/SingletonEventHandler.cs @@ -4,7 +4,7 @@ namespace Sample.Handlers; [SingletonHandler] -public class SingletonEventHandler(IMediator mediator, AppSqliteConnection data) : IEventHandler +public class SingletonEventHandler(AppSqliteConnection data) : IEventHandler { public async Task Handle(MyMessageEvent @event, EventContext context, CancellationToken cancellationToken) { From 7abfdcee90b275694902aa942b318ad75b2b6b19 Mon Sep 17 00:00:00 2001 From: Allan Ritchie Date: Tue, 28 Jan 2025 20:54:24 -0500 Subject: [PATCH 20/20] More minor fixes --- samples/Sample/MauiProgram.cs | 15 ++++++++------- samples/Sample/TriggerViewModel.cs | 3 +-- .../AppSupportExecutionContextExtensions.cs | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/samples/Sample/MauiProgram.cs b/samples/Sample/MauiProgram.cs index 5352981..6a11893 100644 --- a/samples/Sample/MauiProgram.cs +++ b/samples/Sample/MauiProgram.cs @@ -59,11 +59,11 @@ public static MauiApp CreateMauiApp() .AddResiliencyMiddleware(builder.Configuration) // Cache - you can only have one - // .AddPersistentCache() - .AddMemoryCaching(y => - { - y.ExpirationScanFrequency = TimeSpan.FromSeconds(5); - }) + .AddMauiPersistentCache() + // .AddMemoryCaching(y => + // { + // y.ExpirationScanFrequency = TimeSpan.FromSeconds(5); + // }) ); builder.Services.AddSingletonAsImplementedInterfaces(); builder.Services.AddDiscoveredMediatorHandlersFromSample(); @@ -75,7 +75,8 @@ public static MauiApp CreateMauiApp() builder.Services.RegisterForNavigation(); builder.Services.RegisterForNavigation(); builder.Services.RegisterForNavigation(); - - return builder.Build(); + + var app = builder.Build(); + return app; } } \ No newline at end of file diff --git a/samples/Sample/TriggerViewModel.cs b/samples/Sample/TriggerViewModel.cs index 55f192d..7ce30be 100644 --- a/samples/Sample/TriggerViewModel.cs +++ b/samples/Sample/TriggerViewModel.cs @@ -9,7 +9,6 @@ namespace Sample; public partial class TriggerViewModel( IMediator mediator, - IMemoryCache cache, AppSqliteConnection data, IPageDialogService dialogs ) : ObservableObject, IEventHandler @@ -111,7 +110,7 @@ async Task CacheRequest() [RelayCommand] async Task CacheClear() { - cache.Clear(); + await mediator.Publish(new FlushAllStoresEvent()); await dialogs.DisplayAlertAsync("Cache Cleared", "DONE", "Ok"); } [ObservableProperty] string cacheValue; diff --git a/src/Shiny.Mediator.AppSupport/AppSupportExecutionContextExtensions.cs b/src/Shiny.Mediator.AppSupport/AppSupportExecutionContextExtensions.cs index 45f99a9..88d5f11 100644 --- a/src/Shiny.Mediator.AppSupport/AppSupportExecutionContextExtensions.cs +++ b/src/Shiny.Mediator.AppSupport/AppSupportExecutionContextExtensions.cs @@ -14,7 +14,7 @@ public static class AppSupportExecutionContextExtensions internal static void UserErrorNotification(this IMediatorContext context, UserErrorNotificationContext info) => context.Add(nameof(UserErrorNotification), info); - public static OfflineAvailableContext? Offline(this IMediatorContext context) + public static OfflineAvailableContext? Offline(this RequestContext context) => context.TryGetValue("Offline"); internal static void Offline(this RequestContext context, OfflineAvailableContext offlineContext)