Skip to content

Commit

Permalink
Part 1 - Request Context #4
Browse files Browse the repository at this point in the history
  • Loading branch information
aritchie committed Sep 28, 2024
1 parent 1991434 commit c35dbe3
Show file tree
Hide file tree
Showing 19 changed files with 133 additions and 70 deletions.
8 changes: 7 additions & 1 deletion samples/Sample/Handlers/MyRequestMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ namespace Sample.Handlers;
[SingletonMiddleware]
public class MyRequestMiddleware(AppSqliteConnection conn) : IRequestMiddleware<MyMessageRequest, MyMessageResponse>
{
public async Task<MyMessageResponse> Process(MyMessageRequest request, RequestHandlerDelegate<MyMessageResponse> next, IRequestHandler requestHandler, CancellationToken cancellationToken)
public async Task<MyMessageResponse> Process(
MyMessageRequest request,
RequestHandlerDelegate<MyMessageResponse> next,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
)
{
var sw = Stopwatch.StartNew();
var result = await next().ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace Shiny.Mediator;


public static class AppSupportRequestContextExtensions
{
public static Exception? UserErrorNotificationException(this IRequestContext context)
{
if (context.Values.TryGetValue(nameof(UserErrorNotificationException), out var exception))
return (Exception)exception;

return null;
}

public static (string Title, string Message)? UserErrorNotificationAlert(this IRequestContext context)
{
if (context.Values.TryGetValue(nameof(UserErrorNotificationAlert), out var exception))
return ((string Title, string Message))exception;

return null;
}

internal static void SetUserErrorNotificationException(this IRequestContext context, Exception exception)
=> context.Add(nameof(UserErrorNotificationException), exception);

internal static void SetUserErrorNotificationAlert(this IRequestContext context, (string Title, string Message) alert)
=> context.Add(nameof(UserErrorNotificationAlert), alert);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public async Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
)
{
Expand All @@ -28,14 +29,17 @@ CancellationToken cancellationToken
result = await next().ConfigureAwait(false);
if (this.IsEnabled(requestHandler, request))
{
await storage.Store(request!, result);
logger.LogDebug("Cache Store - {Request}", request);
var timestampedResult = new TimestampedResult<TResult>(DateTimeOffset.UtcNow, result);
await storage.Store(request!, timestampedResult);
logger.LogDebug("Offline Store - {Request}", request);
}
}
else
{
result = await storage.Get<TResult>(request!);
logger.LogDebug("Cache Hit - {Request}", request);
var timestampedResult = await storage.Get<TimestampedResult<TResult>>(request!);
context.SetOfflineTimestamp(timestampedResult!.Timestamp);
result = timestampedResult!.Value;
logger.LogDebug("Offline Hit: {Request} - Timestamp: {Timestamp}", request, timestampedResult.Value);
}
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public async Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
)
{
Expand All @@ -32,6 +33,7 @@ CancellationToken cancellationToken
catch (Exception ex)
{
logger.LogError(ex, "Error executing pipeline for {Error}", typeof(TRequest).FullName);
context.SetUserErrorNotificationException(ex);

var key = CultureInfo.CurrentUICulture.Name.ToLower();
var locale = section.GetSection(key);
Expand All @@ -46,6 +48,8 @@ CancellationToken cancellationToken
{
var title = locale.GetValue<string>("Title", "ERROR");
var msg = locale.GetValue<string>("Message", "An error occurred with your request");
context.SetUserErrorNotificationAlert((title, msg)!);

alerts.Display(title!, msg!);
}
}
Expand Down
12 changes: 8 additions & 4 deletions src/Shiny.Mediator.AppSupport/OfflineAvailableModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ public class OfflineAvailableAttribute : Attribute;
public record OfflineAvailableFlushRequest : IRequest;


// public static class OfflineExtensions
// {
// public static DateTimeOffset? Timestamp(this RequestContext context)
// }
public static class OfflineExtensions
{
public static DateTimeOffset? OfflineTimestamp(this IRequestContext context)
=> context.TryGetValue<DateTimeOffset>("Offline.Timestamp");

internal static void SetOfflineTimestamp(this IRequestContext context, DateTimeOffset timestamp)
=> context.Add("Offline.Timestamp", timestamp);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public async Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public async Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public async Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
)
{
Expand Down
23 changes: 23 additions & 0 deletions src/Shiny.Mediator/IRequestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Shiny.Mediator;

public interface IRequestContext
{
Guid ExecutionId { get; }
IReadOnlyDictionary<string, object> Values { get; }
void Add(string key, object value);

IRequestHandler RequestHandler { get; }
}

// TODO: RequestStreamContext OR reuse IRequestContext?
// public class EventContext
// {
// // TODO: event types that were fired
// // TODO: what about middleware that fired per event?
// public Guid ExecutionId { get; } = Guid.NewGuid();
// public IReadOnlyDictionary<string, object> Store => this.store;
//
// readonly Dictionary<string, object> store = new();
// public void Add(string key, object value)
// => this.store.Add(key, value);
// }
4 changes: 2 additions & 2 deletions src/Shiny.Mediator/IRequestMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using Shiny.Mediator;


public delegate Task<TResult> RequestHandlerDelegate<TResult>();
public interface IRequestMiddleware<TRequest, TResult>
{
Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
);
}
16 changes: 16 additions & 0 deletions src/Shiny.Mediator/Impl/RequestContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Shiny.Mediator.Impl;

public class RequestContext(IRequestHandler handler) : IRequestContext
{
readonly Dictionary<string, object> store = new();

public Guid ExecutionId { get; }= Guid.NewGuid();
public IReadOnlyDictionary<string, object> Values => this.store.ToDictionary();
public void Add(string key, object value) => this.store.Add(key, value);

public IRequestHandler RequestHandler => handler;
// public object Request { get; }
// public object? ReturnValue { get; set; }

// public TResult? Result { get; set; }
}
2 changes: 2 additions & 0 deletions src/Shiny.Mediator/Impl/RequestExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ CancellationToken cancellationToken
)
{
var middlewares = services.GetServices<IRequestMiddleware<TRequest, TResult>>();
var context = new RequestContext(requestHandler);

var result = await middlewares
.Reverse()
Expand All @@ -31,6 +32,7 @@ CancellationToken cancellationToken
request,
next,
requestHandler,
context,
cancellationToken
);
})
Expand Down
2 changes: 1 addition & 1 deletion src/Shiny.Mediator/Impl/RequestWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public async Task<TResult> Handle(IServiceProvider services, TRequest request, C
var requestHandler = services.GetService<IRequestHandler<TRequest, TResult>>();
if (requestHandler == null)
throw new InvalidOperationException("No request handler found for " + request.GetType().FullName);

var logger = services.GetRequiredService<ILogger<TRequest>>();
var handlerExec = new RequestHandlerDelegate<TResult>(() =>
{
Expand Down
21 changes: 7 additions & 14 deletions src/Shiny.Mediator/Infrastructure/IRequestSender.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,16 @@ Task<TResult> Request<TResult>(
CancellationToken cancellationToken = default
);


// /// <summary>
// /// Returns the context routes of
// /// </summary>
// /// <param name="request"></param>
// /// <param name="cancellationToken"></param>
// /// <typeparam name="TResult"></typeparam>
// /// <returns></returns>
// Task<RequestContext<TResult>> RequestToContext<TResult>(
// Task<RequestContext> SendContext(IRequest request, CancellationToken cancellationToken = default);
// Task<RequestContext<TResult>> RequestWithContext<TResult>(
// Task<TResult> RequestWithContext<TResult>(
// IRequest<TResult> request,
// CancellationToken cancellationToken = default
// CancellationToken cancellationToken = default,
// out RequestContext context
// );

/// <summary>
///
/// Send a `void` return request
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
Expand All @@ -41,9 +36,7 @@ Task Send(
CancellationToken cancellationToken = default
);


// Task SendReturnContext


/// <summary>
///
/// </summary>
Expand Down
42 changes: 0 additions & 42 deletions src/Shiny.Mediator/MediatorContexts.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public async Task<TResult> Process(
TRequest request,
RequestHandlerDelegate<TResult> next,
IRequestHandler requestHandler,
IRequestContext context,
CancellationToken cancellationToken
)
{
Expand Down
19 changes: 19 additions & 0 deletions src/Shiny.Mediator/RequestContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Shiny.Mediator;

public static class RequestContextExtensions
{
public static T? TryGetValue<T>(this IRequestContext context, string key)
{
if (context.Values.TryGetValue(key, out var value) && value is T t)
return t;

return default;
}


public static TimeSpan? PerformanceLoggingThresholdBreached(this IRequestContext context)
=> context.TryGetValue<TimeSpan>("PerformanceLogging.Breach");

internal static void SetPerformanceLoggingThresholdBreached(this IRequestContext context, TimeSpan elapsed)
=> context.Add("PerformanceLogging.Breach", elapsed);
}
4 changes: 2 additions & 2 deletions tests/Shiny.Mediator.Tests/MiddlewareTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public static class Executed
}
public class ConstrainedMiddleware : IRequestMiddleware<MiddlewareResultRequest, int>
{
public Task<int> Process(MiddlewareResultRequest request, RequestHandlerDelegate<int> next, IRequestHandler requestHandler, CancellationToken cancellationToken)
public Task<int> Process(MiddlewareResultRequest request, RequestHandlerDelegate<int> next, IRequestHandler requestHandler, IRequestContext context, CancellationToken cancellationToken)
{
Executed.Constrained = true;
return next();
Expand All @@ -70,7 +70,7 @@ public Task<int> Process(MiddlewareResultRequest request, RequestHandlerDelegate
public class VariantRequestMiddleware<TRequest, TResult> : IRequestMiddleware<TRequest, TResult>
where TRequest : IRequest<TResult>
{
public Task<TResult> Process(TRequest request, RequestHandlerDelegate<TResult> next, IRequestHandler requestHandler, CancellationToken cancellationToken)
public Task<TResult> Process(TRequest request, RequestHandlerDelegate<TResult> next, IRequestHandler requestHandler, IRequestContext context, CancellationToken cancellationToken)
{
Executed.Variant = true;
return next();
Expand Down
3 changes: 3 additions & 0 deletions tests/Shiny.Mediator.Tests/OfflineAvailableMiddlewareTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
using Shiny.Mediator.Impl;
using Shiny.Mediator.Infrastructure;
using Shiny.Mediator.Middleware;

Expand Down Expand Up @@ -37,11 +38,13 @@ public async Task EndToEnd()
{
this.handler.ReturnValue = 99L;
this.connectivity.IsAvailable = true;
var context = new RequestContext(this.handler);

var func = () => this.middleware.Process(
new OfflineRequest(),
() => this.handler.Handle(new OfflineRequest(), CancellationToken.None),
this.handler,
context,
CancellationToken.None
);

Expand Down

0 comments on commit c35dbe3

Please sign in to comment.