diff --git a/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs b/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs index 8995bd3..20b6f72 100644 --- a/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs +++ b/src/Shiny.Mediator.AppSupport/Middleware/OfflineAvailableRequestMiddleware.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Microsoft.Extensions.Configuration; using Shiny.Mediator.Infrastructure; namespace Shiny.Mediator.Middleware; @@ -7,7 +8,7 @@ namespace Shiny.Mediator.Middleware; public class OfflineAvailableRequestMiddleware( IInternetService connectivity, IStorageService storage, - IFeatureService features + IConfiguration config ) : IRequestMiddleware { public async Task Process( diff --git a/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs b/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs index 65b474f..7376887 100644 --- a/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs +++ b/src/Shiny.Mediator.AppSupport/Middleware/ReplayStreamMiddleware.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using Microsoft.Extensions.Configuration; using Shiny.Mediator.Infrastructure; namespace Shiny.Mediator.Middleware; @@ -11,7 +12,7 @@ namespace Shiny.Mediator.Middleware; /// public class ReplayStreamMiddleware( IStorageService storage, - IFeatureService features + IConfiguration config ) : IStreamRequestMiddleware where TRequest : IStreamRequest { public IAsyncEnumerable Process( diff --git a/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs b/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs index 0f8ab9b..2f23444 100644 --- a/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs +++ b/src/Shiny.Mediator.Blazor/Infrastructure/InternetService.cs @@ -1,12 +1,35 @@ +using Microsoft.JSInterop; using Shiny.Mediator.Infrastructure; namespace Shiny.Mediator.Blazor.Infrastructure; -public class InternetService : IInternetService + +public class InternetService(IJSRuntime jsruntime) : IInternetService, IDisposable { - public bool IsAvailable { get; } - public Task WaitForAvailable(CancellationToken cancelToken = default) + public bool IsAvailable => ((IJSInProcessRuntime)jsruntime).Invoke("navigator.onLine"); + + + [JSInvokable("InternetService.OnStatusChanged")] + public void OnStatusChanged(bool isOnline) { - throw new NotImplementedException(); + if (isOnline) + this.waitSource?.TrySetResult(); } + + + TaskCompletionSource? waitSource; + public async Task WaitForAvailable(CancellationToken cancelToken = default) + { + if (this.IsAvailable) + return; + + var objRef = DotNetObjectReference.Create(this); + ((IJSInProcessRuntime)jsruntime).InvokeVoid("InternetService.subscribe", objRef); + + this.waitSource = new(); + await this.waitSource.Task.ConfigureAwait(false); + this.waitSource = null; + } + + public void Dispose() => ((IJSInProcessRuntime)jsruntime).InvokeVoid("InternetService.unsubscribe"); } \ No newline at end of file diff --git a/src/Shiny.Mediator.Blazor/Infrastructure/StorageService.cs b/src/Shiny.Mediator.Blazor/Infrastructure/StorageService.cs index 618f770..148d445 100644 --- a/src/Shiny.Mediator.Blazor/Infrastructure/StorageService.cs +++ b/src/Shiny.Mediator.Blazor/Infrastructure/StorageService.cs @@ -1,22 +1,73 @@ +using System.Text.Json; +using Microsoft.JSInterop; using Shiny.Mediator.Infrastructure; namespace Shiny.Mediator.Blazor.Infrastructure; -public class StorageService : IStorageService +public class StorageService(IJSRuntime jsruntime) : IStorageService { public Task Store(object request, object result, bool isPeristent) { - throw new NotImplementedException(); + var key = this.GetStoreKeyFromRequest(request); + var store = isPeristent ? "localStorage" : "sessionStorage"; + + var json = JsonSerializer.Serialize(result); + ((IJSInProcessRuntime)jsruntime).Invoke(store + ".setItem", key, json); + + return Task.CompletedTask; } public Task Get(object request, bool isPeristent) { - throw new NotImplementedException(); + var key = this.GetStoreKeyFromRequest(request); + var store = isPeristent ? "localStorage" : "sessionStorage"; + var stringValue = ((IJSInProcessRuntime)jsruntime).Invoke(store + ".getItem", key); + if (String.IsNullOrWhiteSpace(stringValue)) + return null!; + + var final = JsonSerializer.Deserialize(stringValue); + return Task.FromResult(final); } public Task Clear() { - throw new NotImplementedException(); + var inproc = (IJSInProcessRuntime)jsruntime; + inproc.InvokeVoid("localStorage.clear"); + inproc.InvokeVoid("sessionStorage.clear"); + return Task.CompletedTask; } + + + protected virtual string GetStoreKeyFromRequest(object request) + { + if (request is IRequestKey keyProvider) + return keyProvider.GetKey(); + + var t = request.GetType(); + var key = $"{t.Namespace}_{t.Name}"; + + return key; + } + + + // protected virtual string GetPersistentStoreKey(object request, bool createIfNotExists) + // { + // var key = this.GetStoreKeyFromRequest(request); + // this.EnsureKeyLoad(); + // if (this.keys.ContainsKey(key)) + // { + // key = this.keys[key]; + // } + // else if (createIfNotExists) + // { + // var newKey = Guid.NewGuid().ToString(); + // this.keys.Add(key, newKey); + // key = newKey; + // + // this.PersistKeyStore(); + // } + // + // return key; + // } } \ No newline at end of file diff --git a/src/Shiny.Mediator.Blazor/Shiny.Mediator.Blazor.csproj b/src/Shiny.Mediator.Blazor/Shiny.Mediator.Blazor.csproj index 131809e..e878a2c 100644 --- a/src/Shiny.Mediator.Blazor/Shiny.Mediator.Blazor.csproj +++ b/src/Shiny.Mediator.Blazor/Shiny.Mediator.Blazor.csproj @@ -8,6 +8,7 @@ + diff --git a/src/Shiny.Mediator.Blazor/wwwroot/Mediator.js b/src/Shiny.Mediator.Blazor/wwwroot/Mediator.js new file mode 100644 index 0000000..f60ae37 --- /dev/null +++ b/src/Shiny.Mediator.Blazor/wwwroot/Mediator.js @@ -0,0 +1,21 @@ +let handler; + +window.InternetService = { + subscribe: function(interop) { + + handler = function() { + interop.invokeMethodAsync("InternetService.OnStatusChanged", navigator.onLine); + } + + window.addEventListener("online", handler); + window.addEventListener("offline", handler); + }, + + unsubscribe: function() { + if (handler == null) + return; + + window.removeEventListener("online", handler); + window.removeEventListener("offline", handler); + } +}; \ No newline at end of file diff --git a/src/Shiny.Mediator.Caching/Infrastructure/CachingRequestMiddleware.cs b/src/Shiny.Mediator.Caching/Infrastructure/CachingRequestMiddleware.cs index fc76b65..ef1be4f 100644 --- a/src/Shiny.Mediator.Caching/Infrastructure/CachingRequestMiddleware.cs +++ b/src/Shiny.Mediator.Caching/Infrastructure/CachingRequestMiddleware.cs @@ -5,10 +5,7 @@ namespace Shiny.Mediator.Caching.Infrastructure; -public class CachingRequestMiddleware( - IMemoryCache cache, - IFeatureService features -) : IRequestMiddleware +public class CachingRequestMiddleware(IMemoryCache cache) : IRequestMiddleware { public async Task Process( TRequest request, diff --git a/src/Shiny.Mediator.Maui/MauiExtensions.cs b/src/Shiny.Mediator.Maui/MauiExtensions.cs index 069c904..452e0d9 100644 --- a/src/Shiny.Mediator.Maui/MauiExtensions.cs +++ b/src/Shiny.Mediator.Maui/MauiExtensions.cs @@ -90,11 +90,9 @@ public static ShinyConfigurator AddMainThreadMiddleware(this ShinyConfigurator c /// to show a customized message /// /// - /// /// - public static ShinyConfigurator AddUserNotificationExceptionMiddleware(this ShinyConfigurator cfg, UserExceptionRequestMiddlewareConfig? config = null) + public static ShinyConfigurator AddUserNotificationExceptionMiddleware(this ShinyConfigurator cfg) { - cfg.Services.AddSingleton(config ?? new()); cfg.AddOpenRequestMiddleware(typeof(UserExceptionRequestMiddleware<,>)); return cfg; } diff --git a/src/Shiny.Mediator.Maui/Middleware/MainTheadEventMiddleware.cs b/src/Shiny.Mediator.Maui/Middleware/MainTheadEventMiddleware.cs index 4c8a2ea..db56365 100644 --- a/src/Shiny.Mediator.Maui/Middleware/MainTheadEventMiddleware.cs +++ b/src/Shiny.Mediator.Maui/Middleware/MainTheadEventMiddleware.cs @@ -1,12 +1,16 @@ -using System.ComponentModel; -using System.Reflection; +using Shiny.Mediator.Infrastructure; namespace Shiny.Mediator.Middleware; public class MainTheadEventMiddleware : IEventMiddleware where TEvent : IEvent { - public async Task Process(IEvent @event, EventHandlerDelegate next, IEventHandler eventHandler, CancellationToken cancellationToken) + public async Task Process( + IEvent @event, + EventHandlerDelegate next, + IEventHandler eventHandler, + CancellationToken cancellationToken + ) { var attr = eventHandler.GetHandlerHandleMethodAttribute(); diff --git a/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestHandler.cs b/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestHandler.cs index e538da2..f9e1eef 100644 --- a/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestHandler.cs +++ b/src/Shiny.Mediator.Maui/Middleware/MainThreadRequestHandler.cs @@ -1,3 +1,5 @@ +using Shiny.Mediator.Infrastructure; + namespace Shiny.Mediator.Middleware; diff --git a/src/Shiny.Mediator.Maui/Middleware/UserNotificationExceptionRequestMiddleware.cs b/src/Shiny.Mediator.Maui/Middleware/UserNotificationExceptionRequestMiddleware.cs index bd3533e..6465a7e 100644 --- a/src/Shiny.Mediator.Maui/Middleware/UserNotificationExceptionRequestMiddleware.cs +++ b/src/Shiny.Mediator.Maui/Middleware/UserNotificationExceptionRequestMiddleware.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Shiny.Mediator.Infrastructure; @@ -6,11 +7,16 @@ namespace Shiny.Mediator.Middleware; public class UserExceptionRequestMiddleware( - IFeatureService features, - ILogger logger + ILogger logger, + IConfiguration config ) : IRequestMiddleware { - public async Task Process(TRequest request, RequestHandlerDelegate next, IRequestHandler requestHandler, CancellationToken cancellationToken) + public async Task Process( + TRequest request, + RequestHandlerDelegate next, + IRequestHandler requestHandler, + CancellationToken cancellationToken + ) { var attribute = requestHandler.GetHandlerHandleMethodAttribute(); attribute ??= request!.GetType().GetCustomAttribute(); @@ -25,14 +31,14 @@ public async Task Process(TRequest request, RequestHandlerDelegate( - IFeatureService features, + IConfiguration config, ResiliencePipelineProvider pipelineProvider ) : IRequestMiddleware where TRequest : IRequest { diff --git a/src/Shiny.Mediator.SourceGenerators/MediatorSourceGenerator.cs b/src/Shiny.Mediator.SourceGenerators/MediatorSourceGenerator.cs index ef7a735..7320f45 100644 --- a/src/Shiny.Mediator.SourceGenerators/MediatorSourceGenerator.cs +++ b/src/Shiny.Mediator.SourceGenerators/MediatorSourceGenerator.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -76,6 +75,8 @@ public void Execute(GeneratorExecutionContext context) var assName = context.Compilation.AssemblyName?.Replace(".", "_"); var sb = new StringBuilder(); sb + .AppendLine("using Shiny.Mediator;") + .AppendLine() .AppendLine($"namespace {nameSpace};") .AppendLine() .AppendLine("public static class __ShinyMediatorSourceGenExtensions {") diff --git a/src/Shiny.Mediator/Impl/FeatureService.cs b/src/Shiny.Mediator/Impl/FeatureService.cs deleted file mode 100644 index f671148..0000000 --- a/src/Shiny.Mediator/Impl/FeatureService.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System.Reflection; -using Microsoft.Extensions.Configuration; -using Shiny.Mediator.Infrastructure; - -namespace Shiny.Mediator.Impl; - - -public class FeatureService(IConfiguration config) : IFeatureService -{ - public TConfig? GetIfAvailable(object request, object handler) where TConfig : Attribute - { - var attribute = handler - .GetType() - .GetMethod( - "Handle", - BindingFlags.Public | BindingFlags.Instance, - null, - CallingConventions.Any, - [request.GetType(), typeof(CancellationToken)], - null - )! - .GetCustomAttribute(); - - attribute ??= request.GetType().GetCustomAttribute(); - - if (attribute == null) - { - - } - - throw new NotImplementedException(); - } - - public TConfig? GetFromConfigIfAvailable(object request, object handler) - { - var t = request.GetType(); - var key = $"{t.Namespace}.{t.Name}"; - if (config[key] != null) - { - - } - // TODO: backup a namespace - else if (config[$"{t.Namespace}.*"] != null) - { - - } - else if (config["*"] != null) - { - - } - - return default; - } -} -/* -public static TAttribute? GetHandlerHandleMethodAttribute(this IRequestHandler handler) where TAttribute : Attribute - => handler - .GetType() - .GetMethod( - "Handle", - BindingFlags.Public | BindingFlags.Instance, - null, - CallingConventions.Any, - [ typeof(TRequest), typeof(CancellationToken) ], - null - )! - .GetCustomAttribute(); - - -public static TAttribute? GetHandlerHandleMethodAttribute(this IEventHandler handler) - where TEvent : IEvent - where TAttribute : Attribute - => handler - .GetType() - .GetMethod( - "Handle", - BindingFlags.Public | BindingFlags.Instance, - null, - CallingConventions.Any, - [ typeof(TEvent), typeof(CancellationToken) ], - null - )! - .GetCustomAttribute(); - */ \ No newline at end of file diff --git a/src/Shiny.Mediator/Infrastructure/IFeatureService.cs b/src/Shiny.Mediator/Infrastructure/IFeatureService.cs deleted file mode 100644 index 1e90360..0000000 --- a/src/Shiny.Mediator/Infrastructure/IFeatureService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Shiny.Mediator.Infrastructure; - -public interface IFeatureService -{ - TConfig? GetIfAvailable(object request, object handler) where TConfig : Attribute; - TConfig? GetFromConfigIfAvailable(object request, object handler); -} \ No newline at end of file diff --git a/src/Shiny.Mediator/Infrastructure/Utils.cs b/src/Shiny.Mediator/Infrastructure/Utils.cs new file mode 100644 index 0000000..b5815f8 --- /dev/null +++ b/src/Shiny.Mediator/Infrastructure/Utils.cs @@ -0,0 +1,66 @@ +using System.Reflection; +using Microsoft.Extensions.Configuration; + +namespace Shiny.Mediator.Infrastructure; + + +public static class Utils +{ + public static IConfigurationSection GetHandlerSection(this IConfiguration config, object request, object handler) + { + var t = request.GetType(); + var key = $"{t.Namespace}.{t.Name}"; + if (config[key] != null) + { + return config.GetSection(key); + } + + // var t = request.GetType(); + // var key = $"{t.Namespace}.{t.Name}"; + // if (config[key] != null) + // { + // + // } + // // TODO: backup a namespace + // else if (config[$"{t.Namespace}.*"] != null) + // { + // + // } + // else if (config["*"] != null) + // { + // + // } + + return null; + } + + + public static TAttribute? GetHandlerHandleMethodAttribute(this IRequestHandler handler) where TAttribute : Attribute + => handler + .GetType() + .GetMethod( + "Handle", + BindingFlags.Public | BindingFlags.Instance, + null, + CallingConventions.Any, + [ typeof(TRequest), typeof(CancellationToken) ], + null + )! + .GetCustomAttribute(); + + + public static TAttribute? GetHandlerHandleMethodAttribute(this IEventHandler handler) + where TEvent : IEvent + where TAttribute : Attribute + => handler + .GetType() + .GetMethod( + "Handle", + BindingFlags.Public | BindingFlags.Instance, + null, + CallingConventions.Any, + [ typeof(TEvent), typeof(CancellationToken) ], + null + )! + .GetCustomAttribute(); +} \ No newline at end of file diff --git a/src/Shiny.Mediator/MediatorExtensions.cs b/src/Shiny.Mediator/MediatorExtensions.cs index a9aace3..afe7f83 100644 --- a/src/Shiny.Mediator/MediatorExtensions.cs +++ b/src/Shiny.Mediator/MediatorExtensions.cs @@ -1,4 +1,3 @@ -using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; @@ -48,7 +47,6 @@ public static IServiceCollection AddShinyMediator(this IServiceCollection servic cfg.AddTimedMiddleware(); } - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Shiny.Mediator/Middleware/TimedLoggingRequestMiddleware.cs b/src/Shiny.Mediator/Middleware/TimedLoggingRequestMiddleware.cs index 772647c..cb817a9 100644 --- a/src/Shiny.Mediator/Middleware/TimedLoggingRequestMiddleware.cs +++ b/src/Shiny.Mediator/Middleware/TimedLoggingRequestMiddleware.cs @@ -6,8 +6,7 @@ namespace Shiny.Mediator.Middleware; public class TimedLoggingRequestMiddleware( - ILogger logger, - IFeatureService features + ILogger logger ) : IRequestMiddleware { public async Task Process(TRequest request, RequestHandlerDelegate next, IRequestHandler requestHandler, CancellationToken cancellationToken) diff --git a/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs b/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs index 96dd863..e0f5af0 100644 --- a/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs +++ b/src/Shiny.Mediator/Middleware/TimerRefreshStreamRequestMiddleware.cs @@ -1,11 +1,13 @@ using System.Runtime.CompilerServices; +using Microsoft.Extensions.Configuration; using Shiny.Mediator.Infrastructure; namespace Shiny.Mediator.Middleware; public class TimerRefreshStreamRequestMiddleware( - IFeatureService features -) : IStreamRequestMiddleware where TRequest : IStreamRequest + IConfiguration config +) : IStreamRequestMiddleware + where TRequest : IStreamRequest { public IAsyncEnumerable Process( TRequest request, @@ -14,19 +16,33 @@ public IAsyncEnumerable Process( CancellationToken cancellationToken ) { - // var attribute = requestHandler.GetHandlerHandleMethodAttribute(); - // if (attribute == null) - // return next(); + var refreshSeconds = 0; + var attribute = requestHandler.GetHandlerHandleMethodAttribute(); + if (attribute != null) + { + refreshSeconds = attribute.RefreshSeconds; + } + else + { + var section = config.GetHandlerSection(request, this); + if (section.Exists()) + { + section.GetValue("RefreshSeconds", 0); + } + } - return Iterate(attribute, next, cancellationToken); + if (refreshSeconds <= 0) + return next(); + + return this.Iterate(refreshSeconds, next, cancellationToken); } - async IAsyncEnumerable Iterate(TimerRefreshAttribute attribute, StreamRequestHandlerDelegate next, [EnumeratorCancellation] CancellationToken ct) + async IAsyncEnumerable Iterate(int refreshSeconds, StreamRequestHandlerDelegate next, [EnumeratorCancellation] CancellationToken ct) { while (!ct.IsCancellationRequested) { - await Task.Delay(attribute.RefreshSeconds, ct); + await Task.Delay(refreshSeconds, ct); var nxt = next().GetAsyncEnumerator(ct); while (await nxt.MoveNextAsync() && !ct.IsCancellationRequested) diff --git a/src/Shiny.Mediator/Shiny.Mediator.csproj b/src/Shiny.Mediator/Shiny.Mediator.csproj index 44371e0..7049146 100644 --- a/src/Shiny.Mediator/Shiny.Mediator.csproj +++ b/src/Shiny.Mediator/Shiny.Mediator.csproj @@ -13,6 +13,7 @@ + diff --git a/tests/Shiny.Mediator.Tests/ConfigurationTests.cs b/tests/Shiny.Mediator.Tests/ConfigurationTests.cs new file mode 100644 index 0000000..dd4c749 --- /dev/null +++ b/tests/Shiny.Mediator.Tests/ConfigurationTests.cs @@ -0,0 +1,17 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Memory; +using Xunit.Abstractions; + +namespace Shiny.Mediator.Tests; + + +public class ConfigurationTests(ITestOutputHelper output) +{ + [Fact] + public void AddConfiguration() + { + // var config = new ConfigurationManager(); + // config.AddConfiguration(new MemoryConfigurationProvider(new MemoryConfigurationSource().InitialData)); + // config.Get("key1").Should().Be("value1"); + } +} \ No newline at end of file diff --git a/tests/Shiny.Mediator.Tests/OfflineAvailableMiddlewareTests.cs b/tests/Shiny.Mediator.Tests/OfflineAvailableMiddlewareTests.cs index db7e173..2c73e1a 100644 --- a/tests/Shiny.Mediator.Tests/OfflineAvailableMiddlewareTests.cs +++ b/tests/Shiny.Mediator.Tests/OfflineAvailableMiddlewareTests.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Memory; using Shiny.Mediator.Infrastructure; using Shiny.Mediator.Middleware; @@ -10,6 +12,7 @@ public class OfflineAvailableRequestMiddlewareTests readonly MockStorageService storeMgr; readonly OfflineAvailableRequestMiddleware middleware; readonly OfflineRequestHandler handler; + readonly ConfigurationManager config; public OfflineAvailableRequestMiddlewareTests() { @@ -17,9 +20,13 @@ public OfflineAvailableRequestMiddlewareTests() this.connectivity = new(); this.storeMgr = new(); + this.config = new ConfigurationManager(); + // this.config.AddConfiguration(new MemoryConfigurationProvider(new MemoryConfigurationSource().InitialData)) + this.middleware = new OfflineAvailableRequestMiddleware( this.connectivity, - this.storeMgr + this.storeMgr, + null ); } diff --git a/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj b/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj index 1a33de2..63dfa31 100644 --- a/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj +++ b/tests/Shiny.Mediator.Tests/Shiny.Mediator.Tests.csproj @@ -26,6 +26,7 @@ +