Skip to content

Commit

Permalink
Fixes #86 (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeyzimarev authored Apr 21, 2022
1 parent 8eea2fa commit 2f0415a
Show file tree
Hide file tree
Showing 17 changed files with 250 additions and 75 deletions.
7 changes: 7 additions & 0 deletions Eventuous.sln
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Tests.AspNetCore"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Tests.AspNetCore.Web", "src\Extensions\test\Eventuous.Tests.AspNetCore.Web\Eventuous.Tests.AspNetCore.Web.csproj", "{B3F782EE-FBEF-47E2-8379-8A91B11363B8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Eventuous.Sut.AspNetCore", "src\Extensions\test\Eventuous.Sut.AspNetCore\Eventuous.Sut.AspNetCore.csproj", "{1C1033D6-059B-4CEE-A7D8-9EE470053145}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -268,6 +270,10 @@ Global
{B3F782EE-FBEF-47E2-8379-8A91B11363B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B3F782EE-FBEF-47E2-8379-8A91B11363B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B3F782EE-FBEF-47E2-8379-8A91B11363B8}.Release|Any CPU.Build.0 = Release|Any CPU
{1C1033D6-059B-4CEE-A7D8-9EE470053145}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C1033D6-059B-4CEE-A7D8-9EE470053145}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C1033D6-059B-4CEE-A7D8-9EE470053145}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C1033D6-059B-4CEE-A7D8-9EE470053145}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{151A0839-2B1F-49D6-B5DD-199A5FAAB610} = {C60C6094-2A03-45B6-AB33-C514C35DF823}
Expand Down Expand Up @@ -319,5 +325,6 @@ Global
{5555BC0B-418C-49A3-BD68-ADCEBCC518E4} = {0E2520E7-B4A6-47E7-AED8-662C88441A84}
{152E27CE-35F1-4F65-B53A-C7B710F1B310} = {CCD807D2-F4F2-4EC2-A03D-8943F73993A6}
{B3F782EE-FBEF-47E2-8379-8A91B11363B8} = {CCD807D2-F4F2-4EC2-A03D-8943F73993A6}
{1C1033D6-059B-4CEE-A7D8-9EE470053145} = {CCD807D2-F4F2-4EC2-A03D-8943F73993A6}
EndGlobalSection
EndGlobal
1 change: 1 addition & 0 deletions src/Core/test/Eventuous.Tests/Eventuous.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<IsTestProject>true</IsTestProject>
<IncludeSutApp>true</IncludeSutApp>
<IncludeTestHelpers>true</IncludeTestHelpers>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Core/test/Eventuous.Tests/Fixtures/NaiveFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Eventuous.Tests.Fakes;
using Eventuous.TestHelpers.Fakes;

namespace Eventuous.Tests.Fixtures;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Microsoft.AspNetCore.Routing;

namespace Eventuous.AspNetCore.Web;

[PublicAPI]
public class ApplicationServiceRouteBuilder<T> where T : Aggregate {
readonly IEndpointRouteBuilder _builder;

public ApplicationServiceRouteBuilder(IEndpointRouteBuilder builder) => _builder = builder;

/// <summary>
/// Maps the given command type to an HTTP endpoint. The command class can be annotated with
/// the <seealso cref="HttpCommandAttribute"/> if you need a custom route.
/// </summary>
/// <param name="enrichCommand">A function to populate command props from HttpContext</param>
/// <typeparam name="TCommand">Command class</typeparam>
/// <returns></returns>
public ApplicationServiceRouteBuilder<T> MapCommand<TCommand>(
EnrichCommandFromHttpContext<TCommand>? enrichCommand = null
) where TCommand : class {
_builder.MapCommand<TCommand, T>(enrichCommand);
return this;
}

/// <summary>
/// Maps the given command type to an HTTP endpoint using the specified route.
/// </summary>
/// <param name="route">HTTP route for the command</param>
/// <param name="enrichCommand">A function to populate command props from HttpContext</param>
/// <typeparam name="TCommand">Command type</typeparam>
/// <returns></returns>
public ApplicationServiceRouteBuilder<T> MapCommand<TCommand>(
string route,
EnrichCommandFromHttpContext<TCommand>? enrichCommand = null
) where TCommand : class {
_builder.MapCommand<TCommand, T>(route, enrichCommand);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="$(DiagRoot)\Eventuous.Diagnostics\Eventuous.Diagnostics.csproj"/>
<ProjectReference Include="$(DiagRoot)\Eventuous.Diagnostics.Logging\Eventuous.Diagnostics.Logging.csproj"/>
<ProjectReference Include="$(CoreRoot)\Eventuous\Eventuous.csproj"/>
<ProjectReference Include="..\Eventuous.AspNetCore\Eventuous.AspNetCore.csproj"/>
<Using Include="Eventuous"/>
<Using Include="Microsoft.Extensions.DependencyInjection"/>
<Using Include="Microsoft.AspNetCore.Mvc"/>
<ProjectReference Include="$(DiagRoot)\Eventuous.Diagnostics\Eventuous.Diagnostics.csproj" />
<ProjectReference Include="$(DiagRoot)\Eventuous.Diagnostics.Logging\Eventuous.Diagnostics.Logging.csproj" />
<ProjectReference Include="$(CoreRoot)\Eventuous\Eventuous.csproj" />
<ProjectReference Include="..\Eventuous.AspNetCore\Eventuous.AspNetCore.csproj" />
<Using Include="Eventuous" />
<Using Include="Microsoft.Extensions.DependencyInjection" />
<Using Include="Microsoft.AspNetCore.Mvc" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'net6.0'">
<Compile Remove="RouteBuilderExtensions.cs"/>
<Compile Remove="HttpCommandAttribute.cs"/>
<Compile Remove="ResultExtensions.cs"/>
<Compile Remove="RouteBuilderExtensions.cs" />
<Compile Remove="HttpCommandAttribute.cs" />
<Compile Remove="ResultExtensions.cs" />
<Compile Remove="ApplicationServiceRouteBuilder.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,43 @@

namespace Microsoft.AspNetCore.Routing;

public delegate TCommand EnrichCommandFromHttpContext<TCommand>(TCommand command, HttpContext httpContext);

public static class RouteBuilderExtensions {
/// <summary>
/// Allows to add an HTTP endpoint for controller-less apps
/// </summary>
/// <param name="builder">Endpoint route builder instance</param>
/// <param name="enrichCommand">A function to populate command props from HttpContext</param>
/// <typeparam name="TCommand">Command type</typeparam>
/// <typeparam name="TAggregate">Aggregate type on which the command will operate</typeparam>
/// <returns></returns>
[PublicAPI]
public static RouteHandlerBuilder MapCommand<TCommand, TAggregate>(this IEndpointRouteBuilder builder)
where TAggregate : Aggregate where TCommand : class {
public static RouteHandlerBuilder MapCommand<TCommand, TAggregate>(
this IEndpointRouteBuilder builder,
EnrichCommandFromHttpContext<TCommand>? enrichCommand = null
) where TAggregate : Aggregate where TCommand : class {
var attr = typeof(TCommand).GetAttribute<HttpCommandAttribute>();
var route = GetRoute<TCommand>(attr?.Route);
return builder.MapCommand<TCommand, TAggregate>(route);
return builder.MapCommand<TCommand, TAggregate>(route, enrichCommand);
}

/// <summary>
/// Allows to add an HTTP endpoint for controller-less apps
/// </summary>
/// <param name="builder">Endpoint route builder instance</param>
/// <param name="route">HTTP API route</param>
/// <param name="enrichCommand">A function to populate command props from HttpContext</param>
/// <typeparam name="TCommand">Command type</typeparam>
/// <typeparam name="TAggregate">Aggregate type on which the command will operate</typeparam>
/// <returns></returns>
[PublicAPI]
public static RouteHandlerBuilder MapCommand<TCommand, TAggregate>(
this IEndpointRouteBuilder builder,
string route
this IEndpointRouteBuilder builder,
string route,
EnrichCommandFromHttpContext<TCommand>? enrichCommand = null
) where TAggregate : Aggregate where TCommand : class
=> Map<TAggregate, TCommand>(builder, route);
=> Map<TAggregate, TCommand>(builder, route, enrichCommand);

/// <summary>
/// Creates an instance of <see cref="ApplicationServiceRouteBuilder{T}"/> for a given aggregate type, so you
Expand Down Expand Up @@ -79,7 +86,10 @@ void MapAssemblyCommands(Assembly assembly) {
x => x.IsClass && x.CustomAttributes.Any(a => a.AttributeType == attributeType)
);

var method = typeof(RouteBuilderExtensions).GetMethod(nameof(Map), BindingFlags.Static | BindingFlags.NonPublic)!;
var method = typeof(RouteBuilderExtensions).GetMethod(
nameof(Map),
BindingFlags.Static | BindingFlags.NonPublic
)!;

foreach (var type in decoratedTypes) {
var attr = type.GetAttribute<HttpCommandAttribute>()!;
Expand All @@ -91,15 +101,17 @@ void MapAssemblyCommands(Assembly assembly) {

var genericMethod = method.MakeGenericMethod(typeof(TAggregate), type);
genericMethod.Invoke(null, new object?[] { builder, attr.Route });
// Map<TAggregate>(builder, type, attr.Route);
}
}

return builder;
}

static RouteHandlerBuilder Map<TAggregate, TCommand>(IEndpointRouteBuilder builder, string? route)
where TAggregate : Aggregate where TCommand : notnull
static RouteHandlerBuilder Map<TAggregate, TCommand>(
IEndpointRouteBuilder builder,
string? route,
EnrichCommandFromHttpContext<TCommand>? enrichCommand = null
) where TAggregate : Aggregate where TCommand : notnull
=> builder
.MapPost(
GetRoute<TCommand>(route),
Expand All @@ -108,8 +120,9 @@ async Task<IResult>(HttpContext context, IApplicationService<TAggregate> service

if (cmd == null) throw new InvalidOperationException("Failed to deserialize the command");

var result = await service.Handle(cmd, context.RequestAborted);
if (enrichCommand != null) cmd = enrichCommand(cmd, context);

var result = await service.Handle(cmd, context.RequestAborted);
return result.AsResult();
}
)
Expand Down Expand Up @@ -196,32 +209,3 @@ string Generate() {
}
}
}

[PublicAPI]
public class ApplicationServiceRouteBuilder<T> where T : Aggregate {
readonly IEndpointRouteBuilder _builder;

public ApplicationServiceRouteBuilder(IEndpointRouteBuilder builder) => _builder = builder;

/// <summary>
/// Maps the given command type to an HTTP endpoint. The command class can be annotated with
/// the <seealso cref="HttpCommandAttribute"/> if you need a custom route.
/// </summary>
/// <typeparam name="TCommand">Command class</typeparam>
/// <returns></returns>
public ApplicationServiceRouteBuilder<T> MapCommand<TCommand>() where TCommand : class {
_builder.MapCommand<TCommand, T>();
return this;
}

/// <summary>
/// Maps the given command type to an HTTP endpoint using the specified route.
/// </summary>
/// <param name="route">HTTP route for the command</param>
/// <typeparam name="TCommand">Command type</typeparam>
/// <returns></returns>
public ApplicationServiceRouteBuilder<T> MapCommand<TCommand>(string route) where TCommand : class {
_builder.MapCommand<TCommand, T>(route);
return this;
}
}
23 changes: 23 additions & 0 deletions src/Extensions/test/Eventuous.Sut.AspNetCore/BookingService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Eventuous.AspNetCore.Web;
using Eventuous.Sut.Domain;
using NodaTime;

namespace Eventuous.Sut.AspNetCore;

public class BookingService : ApplicationService<Booking, BookingState, BookingId> {
public BookingService(IAggregateStore store, StreamNameMap? streamNameMap = null)
: base(store, streamNameMap: streamNameMap)
=> OnNew<BookRoom>(
(booking, cmd)
=> booking.BookRoom(
new BookingId(cmd.BookingId),
cmd.RoomId,
new StayPeriod(cmd.CheckIn, cmd.CheckOut),
cmd.Price,
cmd.GuestId
)
);
}

[HttpCommand(Route = "book")]
record BookRoom(string BookingId, string RoomId, LocalDate CheckIn, LocalDate CheckOut, decimal Price, string? GuestId);
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<IncludeSutApp>true</IncludeSutApp>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(CoreRoot)\Eventuous\Eventuous.csproj" />
<ProjectReference Include="$(LocalRoot)\Eventuous.AspNetCore.Web\Eventuous.AspNetCore.Web.csproj" />
<ProjectReference Include="$(LocalRoot)\Eventuous.AspNetCore\Eventuous.AspNetCore.csproj" />
</ItemGroup>
<ItemGroup>
<InternalsVisibleTo Include="Eventuous.Tests.AspNetCore.Web" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
</ItemGroup>
</Project>
27 changes: 27 additions & 0 deletions src/Extensions/test/Eventuous.Sut.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System.Text.Json;
using Eventuous.Sut.AspNetCore;
using Eventuous.Sut.Domain;
using Microsoft.AspNetCore.Http.Json;
using NodaTime;
using NodaTime.Serialization.SystemTextJson;
using BookingService = Eventuous.Sut.AspNetCore.BookingService;

DefaultEventSerializer.SetDefaultSerializer(
new DefaultEventSerializer(
new JsonSerializerOptions(JsonSerializerDefaults.Web).ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)
)
);

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationService<BookingService, Booking>();

builder.Services.Configure<JsonOptions>(
options => options.SerializerOptions.ConfigureForNodaTime(DateTimeZoneProviders.Tzdb)
);

var app = builder.Build();

app.MapAggregateCommands<Booking>()
.MapCommand<BookRoom>((cmd, _) => cmd with { GuestId = TestData.GuestId });

app.Run();
5 changes: 5 additions & 0 deletions src/Extensions/test/Eventuous.Sut.AspNetCore/TestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Eventuous.Sut.AspNetCore;

public static class TestData {
public const string GuestId = "test guest";
}
9 changes: 9 additions & 0 deletions src/Extensions/test/Eventuous.Sut.AspNetCore/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFrameworks>net6.0</TargetFrameworks>
<IsTestProject>true</IsTestProject>
<IncludeSutApp>true</IncludeSutApp>
<IncludeTestHelpers>true</IncludeTestHelpers>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="$(CoreRoot)\Eventuous\Eventuous.csproj" />
<ProjectReference Include="$(LocalRoot)\Eventuous.AspNetCore.Web\Eventuous.AspNetCore.Web.csproj" />
<ProjectReference Include="..\Eventuous.Sut.AspNetCore\Eventuous.Sut.AspNetCore.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(LocalRoot)\Eventuous.AspNetCore.Web\Eventuous.AspNetCore.Web.csproj" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.4" />
<PackageReference Include="NodaTime.Serialization.SystemTextJson" Version="1.0.0" />
<PackageReference Include="RestSharp" Version="107.3.0" />
</ItemGroup>
</Project>
Loading

0 comments on commit 2f0415a

Please sign in to comment.