在非 Web 应用中使用 MediatR
📋 概述
MediatR 不仅适用于 Web 应用,还可以在各种非 Web 场景中发挥价值。本文将详细介绍如何在控制台应用、Worker Service、桌面应用和 Blazor 中集成 MediatR。
学习目标
- ✅ 在控制台应用中使用 MediatR
- ✅ Worker Service 后台服务集成
- ✅ WPF/WinForms 桌面应用集成
- ✅ Blazor WebAssembly/Server 集成
- ✅ 理解不同场景的最佳实践
💻 控制台应用集成
基础示例
项目结构
ConsoleApp/
├── Program.cs
├── Commands/
│ └── ProcessDataCommand.cs
├── Handlers/
│ └── ProcessDataHandler.cs
└── Services/
└── IDataProcessor.cs安装 NuGet 包
bash
dotnet new console
dotnet add package MediatR
dotnet add package Microsoft.Extensions.DependencyInjection实现代码
csharp
// Commands/ProcessDataCommand.cs
using MediatR;
public record ProcessDataCommand(string FilePath) : IRequest<ProcessResult>;
public record ProcessResult
{
public bool Success { get; init; }
public int RecordsProcessed { get; init; }
public string? ErrorMessage { get; init; }
}
// Handlers/ProcessDataHandler.cs
public class ProcessDataHandler : IRequestHandler<ProcessDataCommand, ProcessResult>
{
private readonly ILogger<ProcessDataHandler> _logger;
public ProcessDataHandler(ILogger<ProcessDataHandler> logger)
{
_logger = logger;
}
public async Task<ProcessResult> Handle(
ProcessDataCommand request,
CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("开始处理文件: {FilePath}", request.FilePath);
// 模拟数据处理
await Task.Delay(1000, cancellationToken);
var result = new ProcessResult
{
Success = true,
RecordsProcessed = 1000
};
_logger.LogInformation("处理完成: {RecordsProcessed} 条记录",
result.RecordsProcessed);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "处理失败");
return new ProcessResult
{
Success = false,
ErrorMessage = ex.Message
};
}
}
}
// Program.cs
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
var services = new ServiceCollection();
// 配置日志
services.AddLogging(builder =>
{
builder.AddConsole();
builder.SetMinimumLevel(LogLevel.Information);
});
// 注册 MediatR
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
// 注册其他服务
services.AddSingleton<IDataProcessor, DataProcessor>();
var serviceProvider = services.BuildServiceProvider();
// 使用 MediatR
var mediator = serviceProvider.GetRequiredService<IMediator>();
Console.WriteLine("开始数据处理...");
var result = await mediator.Send(new ProcessDataCommand("data.csv"));
if (result.Success)
{
Console.WriteLine($"成功处理 {result.RecordsProcessed} 条记录");
}
else
{
Console.WriteLine($"处理失败: {result.ErrorMessage}");
}
Console.WriteLine("按任意键退出...");
Console.ReadKey();高级示例:带管道行为
csharp
// Behaviors/LoggingBehavior.cs
public class LoggingBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<LoggingBehavior<TRequest, TResponse>> _logger;
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_logger.LogInformation("执行命令: {RequestType}", typeof(TRequest).Name);
var stopwatch = Stopwatch.StartNew();
try
{
var response = await next();
stopwatch.Stop();
_logger.LogInformation("命令执行成功,耗时: {ElapsedMs}ms",
stopwatch.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
stopwatch.Stop();
_logger.LogError(ex, "命令执行失败,耗时: {ElapsedMs}ms",
stopwatch.ElapsedMilliseconds);
throw;
}
}
}
// Behaviors/RetryBehavior.cs
public class RetryBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
private readonly ILogger<RetryBehavior<TRequest, TResponse>> _logger;
private const int MaxRetries = 3;
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
Exception? lastException = null;
for (int i = 0; i < MaxRetries; i++)
{
try
{
return await next();
}
catch (Exception ex)
{
lastException = ex;
_logger.LogWarning(ex,
"执行失败,第 {RetryCount} 次重试", i + 1);
if (i < MaxRetries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)),
cancellationToken);
}
}
}
throw lastException!;
}
}
// 注册
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>));
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RetryBehavior<,>));🔧 Windows Service / Worker Service
Background Service 集成
项目结构
WorkerService/
├── Program.cs
├── Worker.cs
├── Commands/
│ └── ProcessQueueCommand.cs
└── Handlers/
└── ProcessQueueHandler.cs创建 Worker Service
bash
dotnet new worker
dotnet add package MediatR实现代码
csharp
// Commands/ProcessQueueCommand.cs
public record ProcessQueueCommand : IRequest<Unit>
{
public string QueueName { get; init; }
public int BatchSize { get; init; } = 10;
}
// Handlers/ProcessQueueHandler.cs
public class ProcessQueueHandler : IRequestHandler<ProcessQueueCommand, Unit>
{
private readonly ILogger<ProcessQueueHandler> _logger;
private readonly IQueueService _queueService;
public ProcessQueueHandler(
ILogger<ProcessQueueHandler> logger,
IQueueService queueService)
{
_logger = logger;
_queueService = queueService;
}
public async Task<Unit> Handle(
ProcessQueueCommand request,
CancellationToken cancellationToken)
{
_logger.LogInformation("开始处理队列: {QueueName}, 批次大小: {BatchSize}",
request.QueueName,
request.BatchSize);
var messages = await _queueService.DequeueAsync(
request.QueueName,
request.BatchSize,
cancellationToken);
foreach (var message in messages)
{
try
{
await ProcessMessageAsync(message, cancellationToken);
await _queueService.AcknowledgeAsync(message.Id, cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "处理消息失败: {MessageId}", message.Id);
await _queueService.NackAsync(message.Id, cancellationToken);
}
}
_logger.LogInformation("队列处理完成: {QueueName}", request.QueueName);
return Unit.Value;
}
private async Task ProcessMessageAsync(QueueMessage message, CancellationToken ct)
{
// 处理消息逻辑
await Task.Delay(100, ct);
}
}
// Worker.cs
public class Worker : BackgroundService
{
private readonly IMediator _mediator;
private readonly ILogger<Worker> _logger;
private readonly IConfiguration _configuration;
public Worker(
IMediator mediator,
ILogger<Worker> logger,
IConfiguration configuration)
{
_mediator = mediator;
_logger = logger;
_configuration = configuration;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Worker 启动于: {Time}", DateTimeOffset.Now);
while (!stoppingToken.IsCancellationRequested)
{
try
{
// 定期处理队列
await _mediator.Send(new ProcessQueueCommand
{
QueueName = _configuration["QueueName"] ?? "default",
BatchSize = 10
}, stoppingToken);
// 等待一段时间后再次检查
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
catch (OperationCanceledException)
{
// 正常取消
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Worker 执行出错");
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
_logger.LogInformation("Worker 停止于: {Time}", DateTimeOffset.Now);
}
}
// Program.cs
using MediatR;
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
// 注册 MediatR
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
// 注册其他服务
services.AddSingleton<IQueueService, RabbitMqQueueService>();
// 注册 Worker
services.AddHostedService<Worker>();
})
.Build();
await host.RunAsync();定时任务调度
csharp
// Workers/ScheduledWorker.cs
public class ScheduledWorker : BackgroundService
{
private readonly IMediator _mediator;
private readonly ILogger<ScheduledWorker> _logger;
private Timer? _timer;
public ScheduledWorker(IMediator mediator, ILogger<ScheduledWorker> logger)
{
_mediator = mediator;
_logger = logger;
}
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("定时任务 Worker 启动");
// 每天凌晨2点执行
_timer = new Timer(async _ => await ExecuteScheduledTasks(),
null,
TimeSpan.Zero,
TimeSpan.FromHours(24));
return Task.CompletedTask;
}
private async Task ExecuteScheduledTasks()
{
try
{
_logger.LogInformation("执行定时任务");
// 清理过期数据
await _mediator.Send(new CleanupExpiredDataCommand());
// 生成日报
await _mediator.Send(new GenerateDailyReportCommand());
// 发送通知
await _mediator.Send(new SendDailyNotificationCommand());
_logger.LogInformation("定时任务执行完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "定时任务执行失败");
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("定时任务 Worker 停止");
_timer?.Change(Timeout.Infinite, 0);
await base.StopAsync(cancellationToken);
}
public override void Dispose()
{
_timer?.Dispose();
base.Dispose();
}
}🖥️ WPF / WinForms 桌面应用集成
WPF 集成
项目结构
WpfApp/
├── App.xaml.cs
├── MainWindow.xaml.cs
├── ViewModels/
│ └── MainViewModel.cs
├── Commands/
│ └── LoadDataCommand.cs
└── Handlers/
└── LoadDataHandler.cs安装 NuGet 包
bash
dotnet add package MediatR
dotnet add package Microsoft.Extensions.DependencyInjection
dotnet add package CommunityToolkit.Mvvm实现代码
csharp
// App.xaml.cs
using MediatR;
using Microsoft.Extensions.DependencyInjection;
public partial class App : Application
{
public static IServiceProvider ServiceProvider { get; private set; } = default!;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var services = new ServiceCollection();
// 注册 MediatR
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(App).Assembly);
});
// 注册 ViewModel
services.AddTransient<MainViewModel>();
// 注册其他服务
services.AddSingleton<IDataService, DataService>();
ServiceProvider = services.BuildServiceProvider();
// 显示主窗口
var mainWindow = new MainWindow();
mainWindow.Show();
}
}
// ViewModels/MainViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MediatR;
public partial class MainViewModel : ObservableObject
{
private readonly IMediator _mediator;
[ObservableProperty]
private ObservableCollection<OrderDto> _orders = new();
[ObservableProperty]
private bool _isLoading;
[ObservableProperty]
private string? _statusMessage;
public MainViewModel(IMediator mediator)
{
_mediator = mediator;
}
[RelayCommand]
private async Task LoadDataAsync()
{
if (IsLoading) return;
IsLoading = true;
StatusMessage = "正在加载数据...";
try
{
var result = await _mediator.Send(new LoadOrdersQuery());
Orders.Clear();
foreach (var order in result)
{
Orders.Add(order);
}
StatusMessage = $"加载完成,共 {result.Count} 条记录";
}
catch (Exception ex)
{
StatusMessage = $"加载失败: {ex.Message}";
}
finally
{
IsLoading = false;
}
}
[RelayCommand]
private async Task CreateOrderAsync()
{
var dialog = new CreateOrderDialog();
var result = dialog.ShowDialog();
if (result == true)
{
var command = new CreateOrderCommand
{
CustomerName = dialog.CustomerName,
Amount = dialog.Amount
};
await _mediator.Send(command);
// 重新加载数据
await LoadDataAsync();
}
}
}
// Commands/LoadOrdersQuery.cs
public record LoadOrdersQuery : IRequest<List<OrderDto>>;
// Handlers/LoadOrdersHandler.cs
public class LoadOrdersHandler : IRequestHandler<LoadOrdersQuery, List<OrderDto>>
{
private readonly IDataService _dataService;
public LoadOrdersHandler(IDataService dataService)
{
_dataService = dataService;
}
public async Task<List<OrderDto>> Handle(
LoadOrdersQuery request,
CancellationToken cancellationToken)
{
return await _dataService.GetOrdersAsync(cancellationToken);
}
}
// MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = App.ServiceProvider.GetRequiredService<MainViewModel>();
}
}WinForms 集成
csharp
// Program.cs
using MediatR;
using Microsoft.Extensions.DependencyInjection;
static class Program
{
public static IServiceProvider ServiceProvider { get; private set; } = default!;
[STAThread]
static void Main()
{
ApplicationConfiguration.Initialize();
var services = new ServiceCollection();
// 注册 MediatR
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
});
// 注册 Form
services.AddTransient<MainForm>();
ServiceProvider = services.BuildServiceProvider();
Application.Run(ServiceProvider.GetRequiredService<MainForm>());
}
}
// MainForm.cs
public partial class MainForm : Form
{
private readonly IMediator _mediator;
private DataGridView _dataGridView;
private Button _loadButton;
public MainForm(IMediator mediator)
{
_mediator = mediator;
InitializeComponent();
SetupUI();
}
private void SetupUI()
{
_dataGridView = new DataGridView { Dock = DockStyle.Fill };
_loadButton = new Button { Text = "加载数据", Dock = DockStyle.Top };
_loadButton.Click += LoadButton_Click;
Controls.Add(_dataGridView);
Controls.Add(_loadButton);
}
private async void LoadButton_Click(object? sender, EventArgs e)
{
_loadButton.Enabled = false;
try
{
var orders = await _mediator.Send(new LoadOrdersQuery());
_dataGridView.DataSource = orders;
}
catch (Exception ex)
{
MessageBox.Show($"加载失败: {ex.Message}");
}
finally
{
_loadButton.Enabled = true;
}
}
}🌐 Blazor WebAssembly / Server
Blazor Server 集成
项目结构
BlazorApp/
├── Program.cs
├── Pages/
│ └── Orders.razor
├── Components/
│ └── OrderList.razor
└── Features/
├── Orders/
│ ├── Queries/
│ └── Commands/
└── Shared/安装 NuGet 包
bash
dotnet new blazorserver
dotnet add package MediatR实现代码
csharp
// Program.cs
using MediatR;
var builder = WebApplication.CreateBuilder(args);
// 添加 Blazor Server 服务
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// 注册 MediatR(Scoped 生命周期)
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
cfg.Lifetime = ServiceLifetime.Scoped; // Blazor Server 推荐 Scoped
});
// 注册其他服务
builder.Services.AddScoped<IOrderService, OrderService>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();razor
@* Pages/Orders.razor *@
@page "/orders"
@inject IMediator Mediator
@inject NavigationManager Navigation
<h3>订单管理</h3>
@if (isLoading)
{
<p>加载中...</p>
}
else if (errorMessage != null)
{
<div class="alert alert-danger">@errorMessage</div>
}
else
{
<table class="table">
<thead>
<tr>
<th>订单号</th>
<th>客户</th>
<th>金额</th>
<th>操作</th>
</tr>
</thead>
<tbody>
@foreach (var order in orders)
{
<tr>
<td>@order.OrderNumber</td>
<td>@order.CustomerName</td>
<td>¥@order.Amount.ToString("F2")</td>
<td>
<button class="btn btn-sm btn-primary"
@onclick="() => EditOrder(order.Id)">
编辑
</button>
<button class="btn btn-sm btn-danger"
@onclick="() => DeleteOrder(order.Id)">
删除
</button>
</td>
</tr>
}
</tbody>
</table>
<button class="btn btn-success" @onclick="CreateNewOrder">新建订单</button>
}
@code {
private List<OrderDto> orders = new();
private bool isLoading;
private string? errorMessage;
protected override async Task OnInitializedAsync()
{
await LoadOrdersAsync();
}
private async Task LoadOrdersAsync()
{
isLoading = true;
errorMessage = null;
try
{
orders = await Mediator.Send(new ListOrdersQuery());
}
catch (Exception ex)
{
errorMessage = $"加载失败: {ex.Message}";
}
finally
{
isLoading = false;
}
}
private async Task CreateNewOrder()
{
Navigation.NavigateTo("/orders/create");
}
private async Task EditOrder(int id)
{
Navigation.NavigateTo($"/orders/edit/{id}");
}
private async Task DeleteOrder(int id)
{
var confirmed = await JSRuntime.InvokeAsync<bool>(
"confirm", "确定要删除这个订单吗?");
if (confirmed)
{
await Mediator.Send(new DeleteOrderCommand(id));
await LoadOrdersAsync();
}
}
}razor
@* Pages/CreateOrder.razor *@
@page "/orders/create"
@inject IMediator Mediator
@inject NavigationManager Navigation
<h3>创建订单</h3>
<EditForm Model="@orderModel" OnValidSubmit="@HandleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="form-group">
<label>客户名称</label>
<InputText @bind-Value="orderModel.CustomerName" class="form-control" />
</div>
<div class="form-group">
<label>金额</label>
<InputNumber @bind-Value="orderModel.Amount" class="form-control" />
</div>
<button type="submit" class="btn btn-primary" disabled="@isSubmitting">
@(isSubmitting ? "提交中..." : "提交")
</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">取消</button>
</EditForm>
@if (errorMessage != null)
{
<div class="alert alert-danger mt-3">@errorMessage</div>
}
@code {
private OrderFormModel orderModel = new();
private bool isSubmitting;
private string? errorMessage;
private async Task HandleSubmit()
{
isSubmitting = true;
errorMessage = null;
try
{
var command = new CreateOrderCommand
{
CustomerName = orderModel.CustomerName,
Amount = orderModel.Amount
};
await Mediator.Send(command);
Navigation.NavigateTo("/orders");
}
catch (Exception ex)
{
errorMessage = $"创建失败: {ex.Message}";
}
finally
{
isSubmitting = false;
}
}
private void Cancel()
{
Navigation.NavigateTo("/orders");
}
public class OrderFormModel
{
[Required]
[StringLength(100)]
public string CustomerName { get; set; } = "";
[Required]
[Range(0.01, double.MaxValue)]
public decimal Amount { get; set; }
}
}Blazor WebAssembly 集成
csharp
// Program.cs (WebAssembly)
using MediatR;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
// 注册 MediatR
builder.Services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
cfg.Lifetime = ServiceLifetime.Singleton; // WASM 推荐 Singleton
});
// 注册 HTTP 客户端
builder.Services.AddScoped(sp =>
new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();注意:在 WebAssembly 中,所有代码都在浏览器中运行,因此:
- 使用
Singleton生命周期 - 需要通过 HTTP API 调用后端
- Handler 中应该调用 HTTP 客户端而非直接访问数据库
csharp
// Handlers/CreateOrderHandler.cs (WebAssembly)
public class CreateOrderHandler : IRequestHandler<CreateOrderCommand, int>
{
private readonly HttpClient _httpClient;
public CreateOrderHandler(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<int> Handle(
CreateOrderCommand request,
CancellationToken cancellationToken)
{
var response = await _httpClient.PostAsJsonAsync(
"api/orders",
request,
cancellationToken);
response.EnsureSuccessStatusCode();
var orderId = await response.Content.ReadFromJsonAsync<int>(cancellationToken);
return orderId;
}
}🎯 各场景对比总结
| 场景 | 生命周期 | 注意事项 | 推荐度 |
|---|---|---|---|
| 控制台应用 | Singleton | 手动构建 ServiceProvider | ⭐⭐⭐⭐ |
| Worker Service | Singleton | 注意异步取消 | ⭐⭐⭐⭐⭐ |
| WPF | Singleton | UI 线程同步 | ⭐⭐⭐⭐ |
| WinForms | Singleton | UI 线程同步 | ⭐⭐⭐ |
| Blazor Server | Scoped | 每个 Circuit 独立 | ⭐⭐⭐⭐⭐ |
| Blazor WASM | Singleton | 通过 HTTP 调用后端 | ⭐⭐⭐⭐ |
💡 最佳实践
✅ 推荐做法
选择合适的生命周期
- 控制台/桌面应用:Singleton
- Blazor Server:Scoped
- Blazor WASM:Singleton
正确处理取消令牌
csharpprotected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await DoWorkAsync(stoppingToken); // 传递取消令牌 } }UI 线程同步
csharp// WPF Application.Current.Dispatcher.Invoke(() => { // 更新 UI }); // WinForms this.Invoke(() => { // 更新 UI });异常处理
csharptry { await _mediator.Send(command); } catch (Exception ex) { _logger.LogError(ex, "操作失败"); // 向用户显示友好错误消息 }
❌ 避免做法
- 不要在 UI 线程执行耗时操作
- 不要忽略取消令牌
- 不要在 Blazor WASM 中直接访问数据库
- 不要忘记释放资源