Skip to content

Commit

Permalink
Fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
aritchie committed Jun 1, 2024
1 parent e654c90 commit 7c74c25
Show file tree
Hide file tree
Showing 22 changed files with 260 additions and 293 deletions.
2 changes: 0 additions & 2 deletions Sample/EventPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
<Span Text="{Binding Arg, StringFormat='Arg: {0}'}" />
<Span Text=" - " />
<Span Text="{Binding FireAndForget, StringFormat='Fire and Forget: {0}'}" />
<Span Text=" - " />
<Span Text="{Binding ParallelEvents, StringFormat='Parallel Events: {0}'}" />
</FormattedString>
</Label.FormattedText>
</Label>
Expand Down
2 changes: 0 additions & 2 deletions Sample/EventViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ AppSqliteConnection conn
x.Area,
x.Arg,
x.FireAndForget,
x.ParallelEvents,
x.Timestamp.ToLocalTime().ToString("g")
))
.ToList();
Expand Down Expand Up @@ -49,6 +48,5 @@ public record EventItemViewModel(
string Area,
string Arg,
bool FireAndForget,
bool ParallelEvents,
string Timestamp
);
4 changes: 2 additions & 2 deletions Sample/Messages.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Sample;

public record MyMessageRequest(string Arg, bool FireAndForgetEvents, bool ParallelEvents) : IRequest<MyMessageResponse>;
public record MyMessageRequest(string Arg, bool FireAndForgetEvents) : IRequest<MyMessageResponse>;

public record MyMessageResponse(string Response);

public record MyMessageEvent(string Arg, bool FireAndForgetEvents, bool ParallelEvents) : IEvent;
public record MyMessageEvent(string Arg, bool FireAndForgetEvents) : IEvent;
2 changes: 0 additions & 2 deletions Sample/Services/AppSqliteConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ public Task Log(string area, MyMessageEvent @event) => this.InsertAsync(new LogM
Area = area,
Arg = @event.Arg,
FireAndForget = @event.FireAndForgetEvents,
ParallelEvents = @event.ParallelEvents,
Timestamp = DateTimeOffset.UtcNow
});
public AsyncTableQuery<LogModel> Logs => this.Table<LogModel>();
Expand All @@ -31,6 +30,5 @@ public class LogModel
public string Area { get; set; }
public string Arg { get; set; }
public bool FireAndForget { get; set; }
public bool ParallelEvents { get; set; }
public DateTimeOffset Timestamp { get; set; }
}
24 changes: 16 additions & 8 deletions Sample/SingletonRequestHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,24 @@ public async Task<MyMessageResponse> Handle(MyMessageRequest request, Cancellati
// could have a pre/post on handlers
var e = new MyMessageEvent(
request.Arg,
request.FireAndForgetEvents,
request.ParallelEvents
request.FireAndForgetEvents
);
await data.Log("SingletonRequestHandler", e);
await mediator.Publish(
e,
request.FireAndForgetEvents,
request.ParallelEvents,
cancellationToken
);
if (request.FireAndForgetEvents)
{
mediator.Publish(e).RunOffThread(ex =>
{
// TODO: log this
});
}
else
{
await mediator.Publish(
e,
cancellationToken
);
}

return new MyMessageResponse("RESPONSE: " + request.Arg);
}
}
3 changes: 0 additions & 3 deletions Sample/TriggerPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
<EntryCell Label="Argument"
Text="{Binding Arg}" />

<SwitchCell Text="Parallel Events"
On="{Binding ParallelEvents}" />

<SwitchCell Text="Fire and Forget Events"
On="{Binding FireAndForgetEvents}"/>
</TableSection>
Expand Down
10 changes: 3 additions & 7 deletions Sample/TriggerViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,14 @@ AppSqliteConnection data
this.cancelSource = new();
var request = new MyMessageRequest(
this.Arg!,
this.FireAndForgetEvents,
this.ParallelEvents
this.FireAndForgetEvents
);
var response = await mediator.Send(request, this.cancelSource.Token);
await data.Log(
"TriggerViewModel-Response",
new MyMessageEvent(
response.Response,
this.FireAndForgetEvents,
this.ParallelEvents
this.FireAndForgetEvents
)
);
await this.Dialogs.Alert("Execution Complete");
Expand All @@ -45,8 +43,7 @@ await data.Log(
"TriggerViewModel-Cancel",
new MyMessageEvent(
this.Arg,
this.FireAndForgetEvents,
this.ParallelEvents
this.FireAndForgetEvents
)
);
},
Expand All @@ -72,7 +69,6 @@ public Task Handle(MyMessageEvent @event, CancellationToken cancellationToken)
public ICommand CancelCommand { get; }
[Reactive] public string Arg { get; set; }
[Reactive] public bool FireAndForgetEvents { get; set; }
[Reactive] public bool ParallelEvents { get; set; }

public override void Destroy()
{
Expand Down
6 changes: 5 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ dependency injection lifecycle. .NET MAUI generally tends to favor the Messenge
which we won't get into. That being said, we do offer a messenger subscription in our Mediator for where interfaces
and dependency injection can't reach.

This project is heavily inspired by [MediatR](https://github.com/jbogard/mediatr) with some lesser features that we feel
were aimed more at server scenarios, while also adding some features we feel benefit apps

## Features
* A Mediator for your .NET Apps (MAUI & Blazor are the main targets for us)
* Think of "weak" message subscription without the fuss or mess to cleanup
Expand Down Expand Up @@ -169,4 +172,5 @@ Focus on the interfaces from the mediator & the mediator calls itself
* Streams - IAsyncEnumerable or IObservable
* Source Generator Registration
* Need to use a different method or not use extension methods - maybe AddHandlersFromAssemblyName or allow it to be custom named
* IEventHandler<IEvent> can handle ALL events?
* IEventHandler<IEvent> can handle ALL events?
* Request Middleware - Covariance/catch-all
5 changes: 5 additions & 0 deletions src/Shiny.Mediator/IEventCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@ namespace Shiny.Mediator;

public interface IEventCollector
{
/// <summary>
/// Collects IEventHandler types that may be out-of-proc and not part of dependency injection
/// </summary>
/// <typeparam name="TEvent"></typeparam>
/// <returns></returns>
IReadOnlyList<IEventHandler<TEvent>> GetHandlers<TEvent>() where TEvent : IEvent;
}
13 changes: 0 additions & 13 deletions src/Shiny.Mediator/IEventExceptionHandler.cs

This file was deleted.

10 changes: 10 additions & 0 deletions src/Shiny.Mediator/IEventHandler.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
namespace Shiny.Mediator;

/// <summary>
///
/// </summary>
/// <typeparam name="TEvent"></typeparam>
public interface IEventHandler<in TEvent> where TEvent : IEvent
{
/// <summary>
/// /
/// </summary>
/// <param name="event"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task Handle(TEvent @event, CancellationToken cancellationToken);
}
61 changes: 4 additions & 57 deletions src/Shiny.Mediator/IMediator.cs
Original file line number Diff line number Diff line change
@@ -1,61 +1,8 @@
namespace Shiny.Mediator;
using Shiny.Mediator.Infrastructure;

namespace Shiny.Mediator;

public interface IMediator
{
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
Task Send<TRequest>(
TRequest request,
CancellationToken cancellationToken = default
) where TRequest : IRequest;


// Task Send(object arg, CancellationToken cancellationToken = default)
// Task<object?> Send(object arg, CancellationToken cancellationToken = default);

/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TResult"></typeparam>
/// <returns></returns>
Task<TResult> Send<TResult>(
IRequest<TResult> request,
CancellationToken cancellationToken = default
);


/// <summary>
///
/// </summary>
/// <param name="event"></param>
/// <param name="fireAndForget"></param>
/// <param name="executeInParallel"></param>
/// <param name="cancellationToken"></param>
/// <typeparam name="TEvent"></typeparam>
/// <returns></returns>
Task Publish<TEvent>(
TEvent @event,
bool fireAndForget = true,
bool executeInParallel = true,
CancellationToken cancellationToken = default
) where TEvent : IEvent;

// Task Publish(object arg, CancellationToken cancellationToken = default)

/// <summary>
///
/// </summary>
/// <param name="action"></param>
/// <typeparam name="TEvent"></typeparam>
/// <returns></returns>
IDisposable Subscribe<TEvent>(
Func<TEvent, CancellationToken, Task> action
) where TEvent : IEvent;
public interface IMediator : IRequestSender, IEventPublisher
{
}
22 changes: 0 additions & 22 deletions src/Shiny.Mediator/IRequestExceptionHandler.cs

This file was deleted.

26 changes: 13 additions & 13 deletions src/Shiny.Mediator/IRequestMiddleware.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
using Shiny.Mediator;

// TODO: how do I register an "ALL" middleware

// TODO: execution duration timer

// TODO: catch all could be IRequest or IRequest<T>? Could use an IRequest<Void>?
public interface IRequestMiddleware<TRequest, TResult> where TRequest : IRequest<TResult>
{
// intercept with connectivity, if offline go to cache, if online go to remote
// if went to remote, post execute stores to cache
Task<TResult> Process(TRequest request, IRequestHandler<TRequest, TResult> handler);
}
// using Shiny.Mediator;
//
// // TODO: how do I register an "ALL" middleware
//
// // TODO: execution duration timer
//
// // TODO: catch all could be IRequest or IRequest<T>? Could use an IRequest<Void>?
// public interface IRequestMiddleware<TRequest, TResult> where TRequest : IRequest<TResult>
// {
// // intercept with connectivity, if offline go to cache, if online go to remote
// // if went to remote, post execute stores to cache
// Task<TResult> Process(TRequest request, IRequestMiddleware<TRequest, TResult> next, CancellationToken cancellationToken);
// }
55 changes: 55 additions & 0 deletions src/Shiny.Mediator/Impl/DefaultEventPublisher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.Extensions.DependencyInjection;
using Shiny.Mediator.Infrastructure;

namespace Shiny.Mediator.Impl;

public class DefaultEventPublisher(IServiceProvider services, IEnumerable<IEventCollector> collectors) : IEventPublisher
{
readonly SubscriptionEventCollector subscriptions = new();


public async Task Publish<TEvent>(
TEvent @event,
CancellationToken cancellationToken = default
) where TEvent : IEvent
{
// allow registered services to be transient/scoped/singleton
using var scope = services.CreateScope();
var handlers = scope.ServiceProvider.GetServices<IEventHandler<TEvent>>().ToList();
//var globalHandlers = scope.ServiceProvider.GetServices<IEventHandler<IEvent>>().ToList();

AppendHandlersIf(handlers, this.subscriptions);
foreach (var collector in collectors)
AppendHandlersIf(handlers, collector);

if (handlers.Count == 0)
return;

await Task
.WhenAll(
handlers
.Select(x => x.Handle(@event, cancellationToken))
.ToList()
)
.ConfigureAwait(false);
}


public IDisposable Subscribe<TEvent>(Func<TEvent, CancellationToken, Task> action) where TEvent : IEvent
{
var handler = new SubscriptionEventHandler<TEvent>(this.subscriptions);
handler.OnHandle = action;
return handler;
}


static void AppendHandlersIf<TEvent>(List<IEventHandler<TEvent>> list, IEventCollector collector) where TEvent : IEvent
{
var handlers = collector.GetHandlers<TEvent>();
foreach (var handler in handlers)
{
if (!list.Contains(handler))
list.Add(handler);
}
}
}
Loading

0 comments on commit 7c74c25

Please sign in to comment.