Skip to content

Commit

Permalink
Adding some cool middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
aritchie committed Jun 3, 2024
1 parent 4c264fc commit ff1a998
Show file tree
Hide file tree
Showing 10 changed files with 146 additions and 51 deletions.
2 changes: 1 addition & 1 deletion Sample/SingletonRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public async Task<MyMessageResponse> Handle(MyMessageRequest request, Cancellati
await data.Log("SingletonRequestHandler", e);
if (request.FireAndForgetEvents)
{
mediator.Publish(e).RunOffThread(ex =>
mediator.Publish(e).RunInBackground(ex =>
{
// TODO: log this
});
Expand Down
4 changes: 0 additions & 4 deletions src/Shiny.Mediator.Maui/MauiEventCollector.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
namespace Shiny.Mediator;

public static class MauiEventCollectorExtensions
{
public static ShinyConfigurator UseMaui(this ShinyConfigurator cfg) => cfg.AddEventCollector<MauiEventCollector>();
}

public class MauiEventCollector : IEventCollector
{
Expand Down
30 changes: 30 additions & 0 deletions src/Shiny.Mediator.Maui/MauiMediatorExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Shiny.Mediator.Middleware;

namespace Shiny.Mediator;

public static class MauiMediatorExtensions
{
public static ShinyConfigurator UseMaui(this ShinyConfigurator cfg) => cfg.AddEventCollector<MauiEventCollector>();

public static ShinyConfigurator AddTimedMiddleware(this ShinyConfigurator cfg, TimedLoggingMiddlewareConfig config)
{
cfg.Services.AddSingleton(config);
return cfg.AddOpenRequestMiddleware(typeof(TimedLoggingRequestMiddleware<,>));
}



public static ShinyConfigurator AddConnectivityCacheMiddleware(this ShinyConfigurator cfg)
{
cfg.AddOpenRequestMiddleware(typeof(ConnectivityCacheRequestMiddleware<,>));
return cfg;
}


public static ShinyConfigurator AddUserNotificationExceptionMiddleware(this ShinyConfigurator cfg, UserExceptionRequestMiddlewareConfig config)
{
cfg.Services.AddSingleton(config);
cfg.AddOpenRequestMiddleware(typeof(UserExceptionRequestMiddleware<,>));
return cfg;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Text.Json;

namespace Shiny.Mediator.Middleware;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
public class CacheAttribute : Attribute
{
// TODO: duration? configuration?
}


public class ConnectivityCacheRequestMiddleware<TRequest, TResult>(IConnectivity connectivity, IFileSystem fileSystem) : IRequestMiddleware<TRequest, TResult> where TRequest : IRequest<TResult>
{
public async Task<TResult> Process(TRequest request, Func<Task<TResult>> next, CancellationToken cancellationToken)
{
var config = typeof(TRequest).GetCustomAttribute<CacheAttribute>();
if (config == null)
return await next().ConfigureAwait(false);

var result = default(TResult);
var storePath = Path.Combine(fileSystem.CacheDirectory, $"{typeof(TRequest).Name}.cache");

if (connectivity.NetworkAccess != NetworkAccess.Internet)
{
result = JsonSerializer.Deserialize<TResult>(storePath);
}
else
{
result = await next().ConfigureAwait(false);
var json = JsonSerializer.Serialize(result);
await File.WriteAllTextAsync(storePath, json, cancellationToken).ConfigureAwait(false);
}
return result;
}
}
36 changes: 36 additions & 0 deletions src/Shiny.Mediator.Maui/Middleware/TimedMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Diagnostics;
using Microsoft.Extensions.Logging;

namespace Shiny.Mediator.Middleware;


public class TimedLoggingMiddlewareConfig
{
public bool LogAll { get; set; }
public TimeSpan? ErrorThreshold { get; set; }
}

public class TimedLoggingRequestMiddleware<TRequest, TResult>(ILogger<TRequest> logger, TimedLoggingMiddlewareConfig config) : IRequestMiddleware<TRequest, TResult> where TRequest : IRequest<TResult>
{
public async Task<TResult> Process(TRequest request, Func<Task<TResult>> next, CancellationToken cancellationToken)
{
var sw = new Stopwatch();
sw.Start();
var result = await next();
sw.Stop();

var errored = config.ErrorThreshold != null && config.ErrorThreshold < sw.Elapsed;
var msg = $"{typeof(TRequest)} pipeline execution took ${sw.Elapsed}";

if (!errored && config.LogAll)
{
logger.LogDebug(msg);
}
if (errored)
{
logger.LogError(msg);
}
return result;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.Extensions.Logging;

namespace Shiny.Mediator.Middleware;


public class UserExceptionRequestMiddlewareConfig
{
public bool ShowFullException { get; set; }
public string ErrorMessage { get; set; } = "We're sorry. An error has occurred";
public string ErrorTitle { get; set; } = "Error";
public string ErrorConfirm { get; set; } = "OK";
}

public class UserExceptionRequestMiddleware<TRequest, TResult>(ILogger<TRequest> logger, UserExceptionRequestMiddlewareConfig config) : IRequestMiddleware<TRequest, TResult> where TRequest : IRequest<TResult>
{
public async Task<TResult> Process(TRequest request, Func<Task<TResult>> next, CancellationToken cancellationToken)
{
var result = default(TResult);
try
{
result = await next().ConfigureAwait(false);
}
catch (Exception ex)
{
logger.LogError(ex, $"Error executing pipeline for {typeof(TRequest).FullName}");
try
{
var msg = config.ShowFullException ? ex.ToString() : config.ErrorMessage;
await Application.Current!.MainPage!.DisplayAlert(config.ErrorTitle, msg, config.ErrorConfirm);
}
catch
{
// throw away
}
}

return result!;
}
}
10 changes: 1 addition & 9 deletions src/Shiny.Mediator/MediatorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using Shiny.Mediator.Impl;
using Shiny.Mediator.Infrastructure;
using Shiny.Mediator.Middleware;

namespace Shiny.Mediator;


public static class MediatorExtensions
{
public static void RunOffThread(this Task task, Action<Exception> onError)
public static void RunInBackground(this Task task, Action<Exception> onError)
=> task.ContinueWith(x =>
{
if (x.Exception != null)
Expand All @@ -26,14 +25,7 @@ public static IServiceCollection AddShinyMediator(this IServiceCollection servic

return services;
}


public static ShinyConfigurator AddExceptionHandling(this ShinyConfigurator cfg)
=> cfg.AddOpenRequestMiddleware(typeof(ExceptionHandlerMiddleware<,>));

public static ShinyConfigurator AddTimedMiddleware(this ShinyConfigurator cfg)
=> cfg.AddOpenRequestMiddleware(typeof(TimedMiddleware<,>));


public static IServiceCollection AddSingletonAsImplementedInterfaces<TImplementation>(this IServiceCollection services) where TImplementation : class
{
Expand Down
18 changes: 0 additions & 18 deletions src/Shiny.Mediator/Middleware/ExceptionHandlerMiddleware.cs

This file was deleted.

18 changes: 0 additions & 18 deletions src/Shiny.Mediator/Middleware/TimedMiddleware.cs

This file was deleted.

4 changes: 3 additions & 1 deletion src/Shiny.Mediator/ShinyConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Shiny.Mediator;

public sealed class ShinyConfigurator(IServiceCollection services)
{
public IServiceCollection Services => services;

public ShinyConfigurator AddRequestMiddleware<TRequest, TResult, TImpl>(
ServiceLifetime lifetime = ServiceLifetime.Scoped)
where TRequest : IRequest<TResult>
Expand All @@ -31,7 +33,7 @@ public ShinyConfigurator AddRequestMiddleware<TRequest, TResult, TImpl>(

public ShinyConfigurator AddOpenRequestMiddleware(Type implementationType, ServiceLifetime lifetime = ServiceLifetime.Scoped)
{
// validate open generic
// TODO: validate open generic
services.Add(new ServiceDescriptor(typeof(IRequestMiddleware<,>), null, implementationType, lifetime));
return this;
}
Expand Down

0 comments on commit ff1a998

Please sign in to comment.