更新框架
This commit is contained in:
1 parent
ef50fa0620
commit
84b28f9672
72 files changed
+2222
-282
No files matched your search
@@ -0,0 +1,7 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
namespace MAX.Notification;
|
||||
|
||||
/// <summary>
|
||||
/// 标记可发布的通知。
|
||||
/// </summary>
|
||||
public interface INotification { }
|
||||
@@ -0,0 +1,41 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Notification;
|
||||
|
||||
/// <summary>
|
||||
/// 定义通知处理器。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">要处理的通知类型。</typeparam>
|
||||
public interface INotificationHandler<in TNotification>
|
||||
where TNotification : INotification
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理通知。
|
||||
/// </summary>
|
||||
/// <param name="notification">要处理的通知。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
Task Handle(TNotification notification, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步通知处理器的基类。
|
||||
/// </summary>
|
||||
/// <typeparam name="TNotification">通知类型。</typeparam>
|
||||
public abstract class NotificationHandler<TNotification> : INotificationHandler<TNotification>
|
||||
where TNotification : INotification
|
||||
{
|
||||
Task INotificationHandler<TNotification>.Handle(TNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
Handle(notification);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在派生类中重写此方法,实现同步通知处理逻辑。
|
||||
/// </summary>
|
||||
/// <param name="notification">要处理的通知。</param>
|
||||
protected abstract void Handle(TNotification notification);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
|
||||
namespace MAX.Notification;
|
||||
|
||||
public interface INotificationPublisher
|
||||
{
|
||||
Task Publish(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Notification;
|
||||
|
||||
/// <summary>
|
||||
/// 发布通知或事件,交由多个处理器处理。
|
||||
/// </summary>
|
||||
public interface IPublisher
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步向多个处理器分发通知。
|
||||
/// </summary>
|
||||
/// <param name="notification">通知对象。</param>
|
||||
/// <param name="cancellationToken">可选的取消令牌。</param>
|
||||
/// <returns>表示本次通知发布操作的任务。</returns>
|
||||
Task Publish(object notification, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步向多个处理器分发通知。
|
||||
/// </summary>
|
||||
/// <param name="notification">通知对象。</param>
|
||||
/// <param name="cancellationToken">可选的取消令牌。</param>
|
||||
/// <returns>表示本次通知发布操作的任务。</returns>
|
||||
Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
where TNotification : INotification;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
By accessing code under the [Lucky Penny Software GitHub Organization](https://github.com/LuckyPennySoftware) (Lucky Penny Software) here, you are agreeing to the following licensing terms.
|
||||
If you do not agree to these terms, do not access Lucky Penny Software code.
|
||||
|
||||
Your license to Lucky Penny Software source code and/or binaries is governed by the Reciprocal Public License 1.5 (RPL1.5) license as described here:
|
||||
|
||||
https://opensource.org/license/rpl-1-5/
|
||||
|
||||
If you do not wish to release the source of software you build using Lucky Penny Software source code and/or binaries under the terms above, you may use Lucky Penny Software source code and/or binaries under the License Agreement described here:
|
||||
|
||||
https://luckypennysoftware.com/license
|
||||
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Remove="PolySharp" />
|
||||
<None Update="LICENSE.md" Pack="true" PackagePath="" />
|
||||
<None Update="UPSTREAM.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Notification;
|
||||
|
||||
public record NotificationHandlerExecutor(object HandlerInstance, Func<INotification, CancellationToken, Task> HandlerCallback);
|
||||
@@ -0,0 +1,67 @@
|
||||
// 摘自 MediatR 14.2.0 的 Mediator.cs,仅保留通知功能。
|
||||
// 许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
using System.Collections.Concurrent;
|
||||
using MAX.Notification.NotificationPublishers;
|
||||
using MAX.Notification.Wrappers;
|
||||
|
||||
namespace MAX.Notification;
|
||||
|
||||
public class NotificationPublisher : IPublisher
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly INotificationPublisher _publisher;
|
||||
private static readonly ConcurrentDictionary<Type, NotificationHandlerWrapper> _notificationHandlers = new();
|
||||
|
||||
public NotificationPublisher(IServiceProvider serviceProvider)
|
||||
: this(serviceProvider, new ForeachAwaitPublisher()) { }
|
||||
|
||||
public NotificationPublisher(IServiceProvider serviceProvider, INotificationPublisher publisher)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(serviceProvider);
|
||||
ArgumentNullException.ThrowIfNull(publisher);
|
||||
_serviceProvider = serviceProvider;
|
||||
_publisher = publisher;
|
||||
}
|
||||
public Task Publish<TNotification>(TNotification notification, CancellationToken cancellationToken = default)
|
||||
where TNotification : INotification
|
||||
{
|
||||
if (notification == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(notification));
|
||||
}
|
||||
|
||||
return PublishNotification(notification, cancellationToken);
|
||||
}
|
||||
|
||||
public Task Publish(object notification, CancellationToken cancellationToken = default) =>
|
||||
notification switch
|
||||
{
|
||||
null => throw new ArgumentNullException(nameof(notification)),
|
||||
INotification instance => PublishNotification(instance, cancellationToken),
|
||||
_ => throw new ArgumentException($"{nameof(notification)} does not implement ${nameof(INotification)}")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 在派生类中重写此方法可控制任务的等待方式。默认调用 <see cref="INotificationPublisher"/>。
|
||||
/// </summary>
|
||||
/// <param name="handlerExecutors">封装各通知处理器实例及其调用委托的执行器集合。</param>
|
||||
/// <param name="notification">要发布的通知。</param>
|
||||
/// <param name="cancellationToken">取消令牌。</param>
|
||||
/// <returns>表示本次处理器分发操作的任务。</returns>
|
||||
protected virtual Task PublishCore(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken)
|
||||
=> _publisher.Publish(handlerExecutors, notification, cancellationToken);
|
||||
|
||||
private Task PublishNotification(INotification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var handler = _notificationHandlers.GetOrAdd(notification.GetType(), static notificationType =>
|
||||
{
|
||||
var wrapperType = typeof(NotificationHandlerWrapperImpl<>).MakeGenericType(notificationType);
|
||||
var wrapper = Activator.CreateInstance(wrapperType) ?? throw new InvalidOperationException($"Could not create wrapper for type {notificationType}");
|
||||
return (NotificationHandlerWrapper)wrapper;
|
||||
});
|
||||
|
||||
return handler.Handle(notification, _serviceProvider, PublishCore, cancellationToken);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Notification.NotificationPublishers;
|
||||
|
||||
/// <summary>
|
||||
/// 在循环中依次调用并等待各通知处理器:
|
||||
/// <code>
|
||||
/// foreach (var handler in handlers) {
|
||||
/// await handler(notification, cancellationToken);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class ForeachAwaitPublisher : INotificationPublisher
|
||||
{
|
||||
public async Task Publish(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (var handler in handlerExecutors)
|
||||
{
|
||||
await handler.HandlerCallback(notification, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MAX.Notification.NotificationPublishers;
|
||||
|
||||
/// <summary>
|
||||
/// 使用 Task.WhenAll 等待通知处理器任务集合:
|
||||
/// <code>
|
||||
/// var tasks = handlers
|
||||
/// .Select(handler => handler.Handle(notification, cancellationToken))
|
||||
/// .ToList();
|
||||
///
|
||||
/// return Task.WhenAll(tasks);
|
||||
/// </code>
|
||||
/// </summary>
|
||||
public class TaskWhenAllPublisher : INotificationPublisher
|
||||
{
|
||||
public Task Publish(IEnumerable<NotificationHandlerExecutor> handlerExecutors, INotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
var tasks = handlerExecutors
|
||||
.Select(handler => handler.HandlerCallback(notification, cancellationToken))
|
||||
.ToArray();
|
||||
|
||||
return Task.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
# MAX.Notification
|
||||
|
||||
从本地 MediatR 14.2.0 源码抽取的通知组件,仅依赖 .NET 8,不引用任何第三方 NuGet 包。原许可声明完整保存在 LICENSE.md;源码来源和修改记录见 UPSTREAM.md。不包含请求响应、管道、流、许可证检测或容器扫描功能。移除第三方依赖不改变原源码的许可条件。
|
||||
|
||||
## 定义与使用
|
||||
|
||||
```csharp
|
||||
using MAX.Notification;
|
||||
|
||||
public sealed record ProductChanged(int ProductId) : INotification;
|
||||
|
||||
public sealed class RefreshCacheHandler : INotificationHandler<ProductChanged>
|
||||
{
|
||||
public Task Handle(ProductChanged notification, CancellationToken cancellationToken)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
// 更新缓存,或返回真正的异步业务操作。
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
发布器通过标准 `IServiceProvider.GetService(typeof(IEnumerable<INotificationHandler<T>>))` 获取处理器。调用方可自行实现此接口;组件不拥有、不释放容器及处理器。无容器实现可参考测试项目中的 TestServices。
|
||||
|
||||
宿主已经使用 Microsoft DI 时,可以在宿主中注册(以下扩展方法来自宿主自己的 DI 包,本项目无须引用):
|
||||
|
||||
```csharp
|
||||
services.AddTransient<INotificationHandler<ProductChanged>, RefreshCacheHandler>();
|
||||
services.AddScoped<IPublisher>(sp => new NotificationPublisher(sp));
|
||||
// 在有效作用域内获取 IPublisher 后:
|
||||
await publisher.Publish(new ProductChanged(42), cancellationToken);
|
||||
```
|
||||
|
||||
宿主使用 Autofac 时:
|
||||
|
||||
```csharp
|
||||
builder.RegisterType<RefreshCacheHandler>()
|
||||
.As<INotificationHandler<ProductChanged>>();
|
||||
builder.Register(ctx => new NotificationPublisher(
|
||||
new AutofacServiceProvider(ctx.Resolve<ILifetimeScope>())))
|
||||
.As<IPublisher>().InstancePerLifetimeScope();
|
||||
```
|
||||
|
||||
上述 Autofac 示例需要宿主已有 Autofac.Extensions.DependencyInjection,或者宿主自己实现 IServiceProvider 适配器。不要把捕获短期作用域的发布器注册为单例。
|
||||
|
||||
## 行为与边界
|
||||
|
||||
- 默认 ForeachAwaitPublisher:按容器返回顺序逐个等待;Handler 抛异常或返回取消任务后停止,已完成的操作不会回滚。可实现 INotificationPublisher 自定义分发策略。
|
||||
- 并发策略:构造时传入 new MAX.Notification.NotificationPublishers.TaskWhenAllPublisher()。使用原版 Task.WhenAll 实现;Handler 同步抛异常会中断后续枚举,已启动任务不会由此次调用统一等待。返回失败 Task 则会继续启动其他 Handler 并等待全部完成。没有 Task.Run,不保证运行在后台线程,不应并发共享非线程安全资源。
|
||||
- 按通知实际运行时类型解析;不自动查找基类通知处理器,是否支持逆变解析取决于容器。
|
||||
- 一次发布内相同处理器实际类型只执行第一个实例;不同类型各执行一次。这也会合并同类型但不同配置的实例。
|
||||
- 无处理器时成功返回。每次发布重新解析,只缓存类型适配器,不缓存处理器或页面实例。
|
||||
- 取消行为与原版一致:只传递原始 CancellationToken,发布器不主动检查取消。Handler 应自行检查或把令牌传入异步操作;没有 Handler 时,已取消的令牌不会使发布失败。
|
||||
- 无消息去重、持久化、重试、事务、跨进程通信或 UI 线程调度。重复 Publish 仍会重复执行。
|
||||
- 页面关闭不会自动影响 DI 中的处理器;业务 Handler 应与页面生命周期解耦。
|
||||
- 使用运行时泛型反射构建适配器;当前未提供 NativeAOT / trimming 支持保证。
|
||||
|
||||
## 验证
|
||||
|
||||
```powershell
|
||||
dotnet build Source/Core/MAX.Notification/MAX.Notification.csproj -c Release
|
||||
dotnet run --project Tests/MAX.Notification.SmokeTests/MAX.Notification.SmokeTests.csproj -c Release
|
||||
```
|
||||
|
||||
测试项目同样不依赖第三方包,覆盖类型分发、重复处理器、重新解析、空处理器、输入校验、异常、顺序/并发及取消。
|
||||
|
||||
支持继承 NotificationHandler<TNotification> 实现同步处理器,覆写 protected Handle(notification);同步基类本身不检查取消。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 源码来源与修改记录
|
||||
|
||||
来源:用户提供的 `D:\Sources\MediatR-14.2.0`。
|
||||
上游项目:https://github.com/LuckyPennySoftware/MediatR
|
||||
|
||||
LICENSE.md 为原目录同名文件的原样副本,声明 RPL 1.5 或商业许可。移除依赖不改变原许可要求。
|
||||
|
||||
## 直接抽取
|
||||
|
||||
- src/MediatR.Contracts/INotification.cs
|
||||
- src/MediatR/INotificationHandler.cs(含同步 NotificationHandler 基类)
|
||||
- src/MediatR/IPublisher.cs
|
||||
- src/MediatR/INotificationPublisher.cs
|
||||
- src/MediatR/NotificationHandlerExecutor.cs
|
||||
- src/MediatR/NotificationPublishers/ForeachAwaitPublisher.cs
|
||||
- src/MediatR/NotificationPublishers/TaskWhenAllPublisher.cs
|
||||
- src/MediatR/Wrappers/NotificationHandlerWrapper.cs
|
||||
|
||||
以上代码注释已翻译为中文,许可声明保留原文;命名空间由 MediatR 改为 MAX.Notification,并添加来源说明。Wrapper 移除 Microsoft.Extensions.DependencyInjection,GetServices<T>() 改为标准 IServiceProvider.GetService(typeof(IEnumerable<T>)),空结果使用空集合,错误返回类型抛出转换异常。保留原处理器类型去重逻辑。
|
||||
|
||||
NotificationPublisher.cs 从 src/MediatR/Mediator.cs 抽取两个 Publish 重载、PublishCore、PublishNotification、通知缓存及相关字段。入口类改名,保留默认和自定义发布策略构造方式,增加构造参数 null 校验。去除请求响应、流以及第三方许可证检测调用,保留许可声明。
|
||||
|
||||
项目配置、使用说明和测试为新增内容。库不依赖第三方 NuGet 包,也排除全局 PolySharp 引用。
|
||||
|
||||
保留原版行为:只传递 CancellationToken,不主动检查取消;TaskWhenAllPublisher 枚举期间遇到同步异常时停止启动后续处理器。
|
||||
@@ -0,0 +1,32 @@
|
||||
// 摘自 MediatR 14.2.0,许可声明与修改记录见 LICENSE.md 和 UPSTREAM.md。
|
||||
namespace MAX.Notification.Wrappers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
||||
public abstract class NotificationHandlerWrapper
|
||||
{
|
||||
public abstract Task Handle(INotification notification, IServiceProvider serviceFactory,
|
||||
Func<IEnumerable<NotificationHandlerExecutor>, INotification, CancellationToken, Task> publish,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class NotificationHandlerWrapperImpl<TNotification> : NotificationHandlerWrapper
|
||||
where TNotification : INotification
|
||||
{
|
||||
public override Task Handle(INotification notification, IServiceProvider serviceFactory,
|
||||
Func<IEnumerable<NotificationHandlerExecutor>, INotification, CancellationToken, Task> publish,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var handlers = ((IEnumerable<INotificationHandler<TNotification>>?)serviceFactory.GetService(typeof(IEnumerable<INotificationHandler<TNotification>>)) ?? Array.Empty<INotificationHandler<TNotification>>())
|
||||
.GroupBy(static x => x.GetType())
|
||||
.Select(static g => g.First())
|
||||
.Select(static x => new NotificationHandlerExecutor(x, (theNotification, theToken) => x.Handle((TNotification)theNotification, theToken)));
|
||||
|
||||
return publish(handlers, notification, cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user