Skip to content

Commit

Permalink
Refactor composable request handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mauroservienti committed Jun 25, 2020
1 parent 3858ecf commit da42871
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 52 deletions.
30 changes: 29 additions & 1 deletion src/ServiceComposer.AspNetCore/CompositionEndpointBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,35 @@ public CompositionEndpointBuilder(RoutePattern routePattern, IEnumerable<Type> c
_compositionHandlers = compositionHandlers.ToArray();
RoutePattern = routePattern;
Order = order;
RequestDelegate = context => CompositionHandler.ComposeResponse(context, _compositionHandlers);
RequestDelegate = async context =>
{
var (viewModel, statusCode) = await CompositionHandler.HandleComposableRequest(context, _compositionHandlers);
context.Response.StatusCode = statusCode;
var json = (string)JsonConvert.SerializeObject(viewModel, GetSettings(context));
context.Response.ContentType = "application/json; charset=utf-8";
await context.Response.WriteAsync(json);
};
}

JsonSerializerSettings GetSettings(HttpContext context)
{
if (!context.Request.Headers.TryGetValue("Accept-Casing", out StringValues casing))
{
casing = "casing/camel";
}

switch (casing)
{
case "casing/pascal":
return new JsonSerializerSettings();

default: // "casing/camel":
return new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
}
}

public override Endpoint Build()
Expand Down
77 changes: 29 additions & 48 deletions src/ServiceComposer.AspNetCore/CompositionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public class CompositionHandler
}

#if NETCOREAPP3_1
internal static async Task ComposeResponse(HttpContext context, Type[] handlerTypes)
internal static async Task<(dynamic ViewModel, int StatusCode)> HandleComposableRequest(HttpContext context, Type[] handlerTypes)
{
context.Request.EnableBuffering();

Expand All @@ -86,67 +86,48 @@ internal static async Task ComposeResponse(HttpContext context, Type[] handlerTy
context.Response.Headers.AddComposedRequestIdHeader(requestId);

var viewModel = new DynamicViewModel(requestId, routeData, request);
request.SetModel(viewModel);

var handlers = handlerTypes.Select(type => context.RequestServices.GetRequiredService(type)).ToArray();

foreach (var subscriber in handlers.OfType<ICompositionEventsSubscriber>())
try
{
subscriber.Subscribe(viewModel);
}
request.SetModel(viewModel);

var pending = new List<Task>();
var handlers = handlerTypes.Select(type => context.RequestServices.GetRequiredService(type)).ToArray();

foreach (var handler in handlers.OfType<ICompositionRequestsHandler>())
{
pending.Add(handler.Handle(request));
}
foreach (var subscriber in handlers.OfType<ICompositionEventsSubscriber>())
{
subscriber.Subscribe(viewModel);
}

if (pending.Count == 0)
{
context.Response.StatusCode = StatusCodes.Status404NotFound;
}
else
{
try
var pending = handlers.OfType<ICompositionRequestsHandler>()
.Select(handler => handler.Handle(request))
.ToList();

if (pending.Count == 0)
{
await Task.WhenAll(pending);
return (null, StatusCodes.Status404NotFound);
}
catch (Exception ex)
else
{
var errorHandlers = handlers.OfType<ICompositionErrorsHandler>();
foreach (var handler in errorHandlers)
try
{
await handler.OnRequestError(request, ex);
await Task.WhenAll(pending);
}
catch (Exception ex)
{
var errorHandlers = handlers.OfType<ICompositionErrorsHandler>();
foreach (var handler in errorHandlers)
{
await handler.OnRequestError(request, ex);
}

throw;
throw;
}
}
}

context.Response.StatusCode = StatusCodes.Status200OK;
var json = JsonConvert.SerializeObject(viewModel, GetSettings(context));
context.Response.ContentType = "application/json; charset=utf-8";
await context.Response.WriteAsync(json);
}

static JsonSerializerSettings GetSettings(HttpContext context)
{
if (!context.Request.Headers.TryGetValue("Accept-Casing", out StringValues casing))
{
casing = "casing/camel";
return (viewModel, StatusCodes.Status200OK);
}

switch (casing)
finally
{
case "casing/pascal":
return new JsonSerializerSettings();

default: // "casing/camel":
return new JsonSerializerSettings()
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
viewModel.CleanupSubscribers();
}
}
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Routing;

Expand All @@ -19,11 +20,39 @@ public CompositionOverControllersActionFilter(CompositionOverControllersRoutes c
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
var endpoint = context.HttpContext.GetEndpoint() as RouteEndpoint;
var handlerTypes = _compositionOverControllersRoutes.HandlersForRoute(endpoint.RoutePattern.RawText,
context.HttpContext.Request.Method);
if (handlerTypes.Any())
if (endpoint != null)
{
var handlerTypes = _compositionOverControllersRoutes.HandlersForRoute(
endpoint.RoutePattern.RawText,
context.HttpContext.Request.Method);

if (handlerTypes.Any())
{
var (viewModel, statusCode) = await CompositionHandler.HandleComposableRequest(context.HttpContext, handlerTypes);
switch (context.Result)
{
case ViewResult viewResult when viewResult.ViewData.Model == null:
{
//MVC
if (statusCode == StatusCodes.Status200OK)
{
viewResult.ViewData.Model = viewModel;
}

break;
}
case ObjectResult objectResult when objectResult.Value == null:
{
//WebAPI
if (statusCode == StatusCodes.Status200OK)
{
objectResult.Value = viewModel;
}

break;
}
}
}
}

await next();
Expand Down

0 comments on commit da42871

Please sign in to comment.