Skip to content

Commit

Permalink
Add async enumerables to aspnet
Browse files Browse the repository at this point in the history
  • Loading branch information
aritchie committed Jul 8, 2024
1 parent 83f28eb commit 42ac97b
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
23 changes: 23 additions & 0 deletions Sample.Api/Handlers/TheStreamHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using System.Runtime.CompilerServices;

namespace Sample.Api.Handlers;

public record TestStreamRequest(int SecondsBetween) : IStreamRequest<string>;

// swagger does not work well with async enumerables
[ScopedHandler]
[MediatorHttpPost("/stream", Name = "GetStream")]
public class TheStreamHandler(ILogger<TheStreamHandler> logger) : IStreamRequestHandler<TestStreamRequest, string>
{
public async IAsyncEnumerable<string> Handle(TestStreamRequest request, [EnumeratorCancellation] CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
var value = DateTimeOffset.Now.ToString("h:mm:ss tt");
logger.LogInformation("Returning Value: " + value);
yield return value;
await Task.Delay(request.SecondsBetween * 1000, cancellationToken);
}
logger.LogInformation("End of stream requested");
}
}
67 changes: 65 additions & 2 deletions src/Shiny.Mediator.AspNet/WebApplicationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ public static class WebApplicationExtensions
{
static readonly MethodInfo mapVoidType;
static readonly MethodInfo mapResultType;
static readonly MethodInfo mapStreamType;

static WebApplicationExtensions()
{
mapResultType = typeof(WebAppMap).GetMethod(nameof(WebAppMap.MapResultType), BindingFlags.Static | BindingFlags.Public)!;
mapStreamType = typeof(WebAppMap).GetMethod(nameof(WebAppMap.MapStreamType), BindingFlags.Static | BindingFlags.Public)!;
mapVoidType = typeof(WebAppMap).GetMethod(nameof(WebAppMap.MapVoidType), BindingFlags.Static | BindingFlags.Public)!;
}

Expand Down Expand Up @@ -52,7 +54,13 @@ static void TryMap(WebApplication app, Type type)
var attribute = type.GetCustomAttribute<MediatorHttpAttribute>();
if (attribute != null)
MapResult(app, type, attribute);
}
}
else if (IsStreamHandler(type))
{
var attribute = type.GetCustomAttribute<MediatorHttpAttribute>();
if (attribute != null)
MapStream(app, type, attribute);
}
}


Expand All @@ -66,6 +74,11 @@ static bool IsResultHandler(Type type) => type
.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IRequestHandler<,>));


static bool IsStreamHandler(Type type) => type
.GetInterfaces()
.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IStreamRequestHandler<,>));


static void MapVoid(WebApplication app, Type handlerType, MediatorHttpAttribute attribute)
{
var requestType = handlerType
Expand All @@ -79,6 +92,20 @@ static void MapVoid(WebApplication app, Type handlerType, MediatorHttpAttribute
.Invoke(null, [app, attribute]);
}


static void MapStream(WebApplication app, Type handlerType, MediatorHttpAttribute attribute)
{
var requestType = handlerType
.GetInterfaces()
.Where(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IStreamRequestHandler<,>))
.Select(x => x.GetGenericArguments())
.First();

mapStreamType
.MakeGenericMethod(requestType[0], requestType[1])
.Invoke(null, [app, attribute]);
}


static void MapResult(WebApplication app, Type handlerType, MediatorHttpAttribute attribute)
{
Expand Down Expand Up @@ -145,6 +172,42 @@ await mediator
Visit(routerBuilder, attribute);
}


public static void MapStreamType<TRequest, TResult>(WebApplication app, MediatorHttpAttribute attribute) where TRequest : IStreamRequest<TResult>
{
attribute.Tags ??= [$"{typeof(TRequest).Name}s"];
RouteHandlerBuilder routerBuilder;

if (attribute.Method == HttpMethod.Post)
{
routerBuilder = app.MapPost(
attribute.UriTemplate,
(
[FromServices] IMediator mediator,
[FromBody] TRequest request,
CancellationToken cancellationToken
) => mediator.Request(request, cancellationToken)
);
}
else if (attribute.Method == HttpMethod.Put)
{
routerBuilder = app.MapPut(
attribute.UriTemplate,
(
[FromServices] IMediator mediator,
[FromBody] TRequest request,
CancellationToken cancellationToken
) => mediator.Request(request, cancellationToken)
);
}
else
{
throw new InvalidOperationException($"Invalid Mediator Endpoint on `{typeof(TRequest).FullName}` - Can only be PUT/POST");
}

Visit(routerBuilder, attribute);
}


public static void MapResultType<TRequest, TResult>(WebApplication app, MediatorHttpAttribute attribute) where TRequest : IRequest<TResult>
{
Expand All @@ -164,7 +227,7 @@ CancellationToken cancellationToken
var result = await mediator
.Request(request, cancellationToken)
.ConfigureAwait(false);

return Results.Ok(result);
}
);
Expand Down

0 comments on commit 42ac97b

Please sign in to comment.