Skip to content

Commit 3bf3f90

Browse files
authored
Merge pull request #24 from FoundatioFx/cross-project
Add support for using interceptors with handlers in referenced projects
2 parents 405a023 + 07314fe commit 3bf3f90

14 files changed

Lines changed: 1530 additions & 32 deletions

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ Build project (triggers source generators): `dotnet build`
1717
Run tests (validate work): `dotnet test`
1818
Run benchmarks: `cd benchmarks/Foundatio.Mediator.Benchmarks; dotnet run -c Release`
1919
Run samples: `cd samples/ConsoleSample; dotnet run`
20+
Clean (removes source generated files): `dotnet clean`
2021

2122
## Code Conventions
2223

2324
### Coding Standards
2425

2526
- Follow `.editorconfig` settings strictly
26-
- See [.github/instructions/general.instructions.md](.github/instructions/general.instructions.md) for complete guidelines
2727
- Keep comments minimal - only for complex logic or non-obvious intent
2828

2929
### Source Generator Code Style

samples/ModularMonolithSample/src/Orders.Module/Api/OrdersApi.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public static void MapOrdersEndpoints(this IEndpointRouteBuilder endpoints)
6464
var result = await mediator.InvokeAsync<Result<Order>>(command);
6565
return result.ToCreatedResult($"/api/orders/{result.Value?.Id}");
6666
})
67-
.WithName("EntityAction")
67+
.WithName("OrderEntityAction")
6868
.WithSummary("Perform an action on an order");
6969
}
7070
}

samples/ModularMonolithSample/src/Products.Module/Api/ProductApi.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public static void MapProductEndpoints(this IEndpointRouteBuilder endpoints)
6464
var result = await mediator.InvokeAsync<Result<Product>>(command);
6565
return result.ToCreatedResult($"/api/products/{result.Value?.Id}");
6666
})
67-
.WithName("EntityAction")
67+
.WithName("ProductEntityAction")
6868
.WithSummary("Perform an action on a product");
6969
}
7070
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using Foundatio.Mediator;
2+
using Orders.Module.Messages;
3+
using Products.Module.Messages;
4+
5+
namespace WebApp.Api;
6+
7+
public static class DashboardApi
8+
{
9+
public static void MapDashboardEndpoints(this IEndpointRouteBuilder endpoints)
10+
{
11+
var group = endpoints.MapGroup("/api/dashboard")
12+
.WithTags("Dashboard");
13+
14+
group.MapGet("/", async (IMediator mediator) =>
15+
{
16+
var ordersTask = mediator.InvokeAsync<Result<List<Order>>>(new GetOrders());
17+
var productsTask = mediator.InvokeAsync<Result<List<Product>>>(new GetProducts());
18+
19+
await Task.WhenAll(ordersTask.AsTask(), productsTask.AsTask());
20+
21+
var orders = ordersTask.Result;
22+
var products = productsTask.Result;
23+
24+
return Results.Ok(new DashboardResponse(
25+
TotalOrders: orders.IsSuccess ? orders.Value?.Count ?? 0 : 0,
26+
TotalProducts: products.IsSuccess ? products.Value?.Count ?? 0 : 0,
27+
TotalRevenue: orders.IsSuccess ? orders.Value?.Sum(o => o.Amount) ?? 0 : 0,
28+
RecentOrders: orders.IsSuccess ? orders.Value?.OrderByDescending(o => o.CreatedAt).Take(5).ToList() ?? [] : [],
29+
RecentProducts: products.IsSuccess ? products.Value?.OrderByDescending(p => p.CreatedAt).Take(5).ToList() ?? [] : []
30+
));
31+
})
32+
.WithName("GetDashboard")
33+
.WithSummary("Get dashboard overview with orders and products summary");
34+
35+
group.MapPost("/quick-order", async (QuickOrderRequest request, IMediator mediator) =>
36+
{
37+
var command = new CreateOrder(request.CustomerId, request.Amount, request.Description ?? "Quick order");
38+
var result = await mediator.InvokeAsync<Result<Order>>(command);
39+
40+
return result.IsSuccess
41+
? Results.Created($"/api/orders/{result.Value?.Id}", result.Value)
42+
: Results.BadRequest(new { error = result.Message });
43+
})
44+
.WithName("QuickOrder")
45+
.WithSummary("Create a quick order with minimal input");
46+
47+
group.MapPost("/quick-product", async (QuickProductRequest request, IMediator mediator) =>
48+
{
49+
var command = new CreateProduct(request.Name, request.Description ?? $"Product: {request.Name}", request.Price);
50+
var result = await mediator.InvokeAsync<Result<Product>>(command);
51+
52+
return result.IsSuccess
53+
? Results.Created($"/api/products/{result.Value?.Id}", result.Value)
54+
: Results.BadRequest(new { error = result.Message });
55+
})
56+
.WithName("QuickProduct")
57+
.WithSummary("Create a quick product with minimal input");
58+
}
59+
}
60+
61+
// Request/Response DTOs
62+
public record QuickOrderRequest(string CustomerId, decimal Amount, string? Description = null);
63+
public record QuickProductRequest(string Name, decimal Price, string? Description = null);
64+
65+
public record DashboardResponse(
66+
int TotalOrders,
67+
int TotalProducts,
68+
decimal TotalRevenue,
69+
List<Order> RecentOrders,
70+
List<Product> RecentProducts);
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using Foundatio.Mediator;
2+
using Orders.Module.Messages;
3+
using Products.Module.Messages;
4+
5+
namespace WebApp.Api;
6+
7+
public static class SearchApi
8+
{
9+
public static void MapSearchEndpoints(this IEndpointRouteBuilder endpoints)
10+
{
11+
endpoints.MapGet("/api/search", async (string? q, IMediator mediator) =>
12+
{
13+
var ordersTask = mediator.InvokeAsync<Result<List<Order>>>(new GetOrders());
14+
var productsTask = mediator.InvokeAsync<Result<List<Product>>>(new GetProducts());
15+
16+
await Task.WhenAll(ordersTask.AsTask(), productsTask.AsTask());
17+
18+
var orders = ordersTask.Result;
19+
var products = productsTask.Result;
20+
21+
var query = q?.ToLowerInvariant() ?? "";
22+
23+
var matchingOrders = orders.IsSuccess
24+
? orders.Value?.Where(o =>
25+
o.Description.Contains(query, StringComparison.OrdinalIgnoreCase) ||
26+
o.CustomerId.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList() ?? []
27+
: [];
28+
29+
var matchingProducts = products.IsSuccess
30+
? products.Value?.Where(p =>
31+
p.Name.Contains(query, StringComparison.OrdinalIgnoreCase) ||
32+
p.Description.Contains(query, StringComparison.OrdinalIgnoreCase)).ToList() ?? []
33+
: [];
34+
35+
return Results.Ok(new SearchResponse(
36+
Query: q,
37+
Orders: matchingOrders,
38+
Products: matchingProducts,
39+
TotalResults: matchingOrders.Count + matchingProducts.Count
40+
));
41+
})
42+
.WithName("Search")
43+
.WithTags("Search")
44+
.WithSummary("Search across orders and products");
45+
}
46+
}
47+
48+
public record SearchResponse(
49+
string? Query,
50+
List<Order> Orders,
51+
List<Product> Products,
52+
int TotalResults);

samples/ModularMonolithSample/src/WebApp/Program.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using Products.Module;
77
using Products.Module.Api;
88
using Products.Module.Messages;
9+
using WebApp.Api;
910

1011
var builder = WebApplication.CreateBuilder(args);
1112

@@ -40,4 +41,8 @@
4041
.WithName("Home")
4142
.WithSummary("Home endpoint");
4243

44+
// Map WebApp endpoints (these call handlers in other modules via cross-assembly interception)
45+
app.MapDashboardEndpoints();
46+
app.MapSearchEndpoints();
47+
4348
app.Run();

0 commit comments

Comments
 (0)