Skip to content

Commit a5952ef

Browse files
committed
Adding Result support
1 parent 8ee4239 commit a5952ef

File tree

17 files changed

+983
-26
lines changed

17 files changed

+983
-26
lines changed

samples/ConsoleSample/ConsoleSample.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
2424
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
2525
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
26+
<PackageReference Include="MiniValidation" Version="0.8.0" />
2627
</ItemGroup>
2728

2829
</Project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using ConsoleSample.Messages;
2+
using Foundatio.Mediator;
3+
4+
public class CreateUserCommandHandler
5+
{
6+
public async Task<Result<User>> HandleAsync(CreateUserCommand command, CancellationToken cancellationToken = default)
7+
{
8+
// Simulate some business logic
9+
await Task.Delay(100, cancellationToken);
10+
11+
if (command.Email == "existing@example.com")
12+
return Result.Conflict("A user with this email already exists");
13+
14+
// Create the user
15+
var user = new User
16+
{
17+
Id = Random.Shared.Next(1000, 9999),
18+
Name = command.Name,
19+
Email = command.Email,
20+
Age = command.Age,
21+
PhoneNumber = command.PhoneNumber,
22+
CreatedAt = DateTime.UtcNow
23+
};
24+
25+
Console.WriteLine($"✅ [CreateUserCommandHandler] Successfully created user with ID: {user.Id}");
26+
27+
// User implicitly converted to Result<User>
28+
return user;
29+
}
30+
}

samples/ConsoleSample/Handlers/ValidationHandlers.cs

Whitespace-only changes.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.ComponentModel.DataAnnotations;
2+
using Foundatio.Mediator;
3+
4+
namespace ConsoleSample.Messages;
5+
6+
public class CreateUserCommand : ICommand
7+
{
8+
[Required(ErrorMessage = "Name is required")]
9+
[StringLength(50, MinimumLength = 2, ErrorMessage = "Name must be between 2 and 50 characters")]
10+
public string Name { get; set; } = string.Empty;
11+
12+
[Required(ErrorMessage = "Email is required")]
13+
[EmailAddress(ErrorMessage = "Invalid email format")]
14+
public string Email { get; set; } = string.Empty;
15+
16+
[Range(18, 120, ErrorMessage = "Age must be between 18 and 120")]
17+
public int Age { get; set; }
18+
19+
[Phone(ErrorMessage = "Invalid phone number format")]
20+
public string? PhoneNumber { get; set; }
21+
}
22+
23+
public record User
24+
{
25+
public int Id { get; set; }
26+
public string Name { get; set; } = string.Empty;
27+
public string Email { get; set; } = string.Empty;
28+
public int Age { get; set; }
29+
public string? PhoneNumber { get; set; }
30+
public DateTime CreatedAt { get; set; }
31+
}
32+
33+
public record UserCreatedEvent(User User) : INotification
34+
{
35+
public string Id => User.Id.ToString();
36+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using Foundatio.Mediator;
2+
using MiniValidation;
3+
4+
namespace ConsoleSample.Middleware;
5+
6+
/// <summary>
7+
/// Validation middleware that uses MiniValidator to validate message models before handler execution.
8+
/// If validation fails, it short-circuits and returns validation errors as a Result.
9+
/// </summary>
10+
public class ValidationMiddleware
11+
{
12+
/// <summary>
13+
/// Validates the message before handler execution. If validation fails, returns a short-circuit result.
14+
/// </summary>
15+
/// <param name="message">The message to validate.</param>
16+
/// <returns>A HandlerResult that either continues or short-circuits with validation errors.</returns>
17+
public HandlerResult Before(object message)
18+
{
19+
if (!MiniValidator.TryValidate(message, out var errors))
20+
{
21+
// Convert MiniValidator errors to Foundatio.Mediator ValidationErrors
22+
var validationErrors = errors.SelectMany(kvp =>
23+
kvp.Value.Select(errorMessage =>
24+
new ValidationError(kvp.Key, errorMessage))).ToList();
25+
26+
return HandlerResult.ShortCircuit(Result.Invalid(validationErrors));
27+
}
28+
29+
return HandlerResult.Continue();
30+
}
31+
}

samples/ConsoleSample/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@
1313

1414
// Get mediator and run samples
1515
var mediator = host.Services.GetRequiredService<IMediator>();
16-
var sampleRunner = new SampleRunner(mediator);
16+
var sampleRunner = new SampleRunner(mediator, host.Services);
1717

1818
await sampleRunner.RunAllSamplesAsync();

samples/ConsoleSample/SampleRunner.cs

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
using ConsoleSample.Messages;
22
using Foundatio.Mediator;
3+
using Microsoft.Extensions.DependencyInjection;
34

45
namespace ConsoleSample;
56

67
public class SampleRunner
78
{
89
private readonly IMediator _mediator;
10+
private readonly IServiceProvider _serviceProvider;
911

10-
public SampleRunner(IMediator mediator)
12+
public SampleRunner(IMediator mediator, IServiceProvider serviceProvider)
1113
{
1214
_mediator = mediator;
15+
_serviceProvider = serviceProvider;
1316
}
1417

1518
public async Task RunAllSamplesAsync()
@@ -22,6 +25,7 @@ public async Task RunAllSamplesAsync()
2225
await RunPublishSamples();
2326
await RunSingleHandlerInvokeSamples();
2427
await RunMixedSyncAsyncSamples();
28+
await RunValidationSamplesAsync();
2529

2630
Console.WriteLine("🎉 All samples completed successfully!");
2731
}
@@ -94,4 +98,56 @@ private async Task RunMixedSyncAsyncSamples()
9498

9599
Console.WriteLine("✅ Mixed sync/async test completed!\n");
96100
}
101+
102+
public async Task RunValidationSamplesAsync()
103+
{
104+
Console.WriteLine("\n🔍 === Validation Middleware Demonstration ===");
105+
Console.WriteLine("This sample shows how validation middleware integrates with Result types");
106+
Console.WriteLine("The middleware validates input before handlers run, returning validation errors as Results\n");
107+
108+
var createUserCommand = new CreateUserCommand
109+
{
110+
Name = "Sample User",
111+
Email = "missing@example.com",
112+
Age = 35,
113+
PhoneNumber = "123-456-7890"
114+
};
115+
116+
var userResult = await _mediator.InvokeAsync<Result<User>>(createUserCommand);
117+
Console.WriteLine($"✅ User created successfully: {userResult.Value.Name}");
118+
119+
// asking for user will cause any errors to throw since the result type can't be implicitly converted to User
120+
var user = await _mediator.InvokeAsync<User>(createUserCommand);
121+
Console.WriteLine($"✅ User created successfully: {userResult.Value.Name}");
122+
123+
createUserCommand.Email = "existing@example.com";
124+
125+
userResult = await _mediator.InvokeAsync<Result<User>>(createUserCommand);
126+
Console.WriteLine($"❌ User creation failed: {userResult.Errors}");
127+
128+
// asking for user will cause any errors to throw since the result type can't be implicitly converted to User
129+
try
130+
{
131+
user = await _mediator.InvokeAsync<User>(createUserCommand);
132+
}
133+
catch (Exception ex)
134+
{
135+
Console.WriteLine($"❌ Error creating user: {ex.Message}");
136+
}
137+
138+
createUserCommand.Email = String.Empty;
139+
140+
userResult = await _mediator.InvokeAsync<Result<User>>(createUserCommand);
141+
Console.WriteLine($"❌ User creation failed: {userResult.Errors}");
142+
143+
// asking for user will cause any errors to throw since the result type can't be implicitly converted to User
144+
try
145+
{
146+
user = await _mediator.InvokeAsync<User>(createUserCommand);
147+
}
148+
catch (Exception ex)
149+
{
150+
Console.WriteLine($"❌ Error creating user: {ex.Message}");
151+
}
152+
}
97153
}

samples/ConsoleSample/ServiceConfiguration.cs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,6 @@ public static IServiceCollection ConfigureServices(this IServiceCollection servi
2222
// Register mediator and handlers
2323
services.AddMediator();
2424

25-
// Register middleware (no longer needed - middleware will be auto-created)
26-
// services.AddScoped<ConsoleSample.Middleware.ProcessOrderMiddleware>();
27-
// services.AddScoped<ConsoleSample.Middleware.GlobalMiddleware>();
28-
// services.AddScoped<ConsoleSample.Middleware.CommandMiddleware>();
29-
// services.AddScoped<ConsoleSample.Middleware.AnotherGlobalMiddleware>();
30-
// services.AddScoped<ConsoleSample.Middleware.FirstOrderedMiddleware>();
31-
// services.AddScoped<ConsoleSample.Middleware.LastOrderedMiddleware>();
32-
// services.AddScoped<ConsoleSample.Middleware.SyncMiddleware>();
33-
3425
return services;
3526
}
3627
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
namespace Foundatio.Mediator;
2+
3+
/// <summary>
4+
/// Defines the common interface for all Result types.
5+
/// </summary>
6+
public interface IResult
7+
{
8+
/// <summary>
9+
/// Gets the status of the result.
10+
/// </summary>
11+
ResultStatus Status { get; }
12+
13+
/// <summary>
14+
/// Gets a value indicating whether the result represents a successful operation.
15+
/// </summary>
16+
bool IsSuccess { get; }
17+
18+
/// <summary>
19+
/// Gets the type of the result value.
20+
/// </summary>
21+
Type ValueType { get; }
22+
23+
/// <summary>
24+
/// Gets the result value as an object.
25+
/// </summary>
26+
/// <returns>The result value.</returns>
27+
object? GetValue();
28+
}

0 commit comments

Comments
 (0)