mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
refactor(core): 替换Mediator集成实现为通用服务配置机制
- 移除专用的RegisterMediator方法,替换为ExecuteServicesHook通用服务配置方法 - 从架构配置中移除Mediator特定配置选项,改为通用服务配置委托 - 在架构基类中添加Configurator属性支持,允许子类提供自定义服务配置 - 更新测试代码适配新的服务配置方式,通过ExecuteServicesHook注册Mediator - 移除过时的测试组件和相关验证逻辑 - 删除Mediator.SourceGenerator包引用,保留运行时依赖 - 添加WaitUntilReadyAsync方法的详细文档注释
This commit is contained in:
parent
a61c796e4d
commit
e755c5c7f8
@ -26,9 +26,5 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Mediator.Abstractions" Version="3.0.1"/>
|
||||
<PackageReference Include="Mediator.SourceGenerator" Version="3.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.system;
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GFramework.Core.Abstractions.architecture;
|
||||
|
||||
@ -15,6 +16,14 @@ public interface IArchitecture : IAsyncInitializable
|
||||
/// </summary>
|
||||
IArchitectureContext Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置用于配置服务集合的委托
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 一个可为空的委托,用于配置IServiceCollection实例
|
||||
/// </value>
|
||||
Action<IServiceCollection>? Configurator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化方法,用于执行对象的初始化操作
|
||||
/// </summary>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
using GFramework.Core.Abstractions.properties;
|
||||
using Mediator;
|
||||
|
||||
namespace GFramework.Core.Abstractions.architecture;
|
||||
|
||||
@ -17,11 +16,4 @@ public interface IArchitectureConfiguration
|
||||
/// 获取或设置架构选项,包含架构相关的配置参数
|
||||
/// </summary>
|
||||
ArchitectureProperties ArchitectureProperties { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置Mediator配置委托
|
||||
/// 用于自定义Mediator框架的配置选项
|
||||
/// </summary>
|
||||
/// <returns>配置Mediator选项的委托函数,可为null</returns>
|
||||
Action<MediatorOptions>? Configurator { get; set; }
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
using GFramework.Core.Abstractions.rule;
|
||||
using GFramework.Core.Abstractions.system;
|
||||
using Mediator;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GFramework.Core.Abstractions.ioc;
|
||||
@ -80,12 +79,12 @@ public interface IIocContainer : IContextAware
|
||||
void RegisterMediatorBehavior<TBehavior>()
|
||||
where TBehavior : class;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册并配置Mediator框架
|
||||
/// 提供自定义配置选项来调整Mediator的行为
|
||||
/// 配置服务
|
||||
/// </summary>
|
||||
/// <param name="configurator">可选的配置委托函数,用于自定义Mediator选项</param>
|
||||
void RegisterMediator(Action<MediatorOptions>? configurator = null);
|
||||
/// <param name="configurator">服务配置委托</param>
|
||||
void ExecuteServicesHook(Action<IServiceCollection>? configurator = null);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using GFramework.Core.Abstractions.architecture;
|
||||
using GFramework.Core.Abstractions.enums;
|
||||
using GFramework.Core.Abstractions.events;
|
||||
using GFramework.Core.Abstractions.model;
|
||||
using GFramework.Core.Abstractions.system;
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
using GFramework.Core.architecture;
|
||||
using GFramework.Core.command;
|
||||
using GFramework.Core.environment;
|
||||
@ -16,6 +11,7 @@ using GFramework.Core.query;
|
||||
using Mediator;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NUnit.Framework;
|
||||
|
||||
// ✅ Mediator 库的命名空间
|
||||
|
||||
// ✅ 使用 global using 或别名来区分
|
||||
@ -50,28 +46,10 @@ public class MediatorComprehensiveTests
|
||||
_container.RegisterPlurality(_environment);
|
||||
|
||||
// ✅ 注册 Mediator
|
||||
_container.RegisterMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; });
|
||||
|
||||
// ✅ 手动注册 Mediator Handlers
|
||||
_container.Services.AddSingleton<IRequestHandler<TestRequest, int>, TestRequestHandler>();
|
||||
_container.Services.AddSingleton<IRequestHandler<TestCommand, Unit>, TestCommandHandler>();
|
||||
_container.Services.AddSingleton<IRequestHandler<TestCommandWithResult, int>, TestCommandWithResultHandler>();
|
||||
_container.Services.AddSingleton<IRequestHandler<TestQuery, string>, TestQueryHandler>();
|
||||
_container.Services.AddSingleton<INotificationHandler<TestNotification>, TestNotificationHandler>();
|
||||
_container.Services.AddSingleton<IStreamRequestHandler<TestStreamRequest, int>, TestStreamRequestHandler>();
|
||||
|
||||
// 注册测试组件(Legacy)
|
||||
_testSystem = new TestSystem();
|
||||
_testModel = new TestModel();
|
||||
_testUtility = new TestUtility();
|
||||
_testCommand = new TestTraditionalCommand();
|
||||
_testQuery = new TestTraditionalQuery { Result = 999 };
|
||||
|
||||
_container.RegisterPlurality(_testSystem);
|
||||
_container.RegisterPlurality(_testModel);
|
||||
_container.RegisterPlurality(_testUtility);
|
||||
_container.RegisterPlurality(_testCommand);
|
||||
_container.RegisterPlurality(_testQuery);
|
||||
_container.ExecuteServicesHook(configurator =>
|
||||
{
|
||||
configurator.AddMediator(options => { options.ServiceLifetime = ServiceLifetime.Singleton; });
|
||||
});
|
||||
|
||||
// ✅ Freeze 容器
|
||||
_container.Freeze();
|
||||
@ -89,11 +67,6 @@ public class MediatorComprehensiveTests
|
||||
_queryBus = null;
|
||||
_asyncQueryBus = null;
|
||||
_environment = null;
|
||||
_testSystem = null;
|
||||
_testModel = null;
|
||||
_testUtility = null;
|
||||
_testCommand = null;
|
||||
_testQuery = null;
|
||||
}
|
||||
|
||||
private ArchitectureContext? _context;
|
||||
@ -103,11 +76,6 @@ public class MediatorComprehensiveTests
|
||||
private QueryExecutor? _queryBus;
|
||||
private AsyncQueryExecutor? _asyncQueryBus;
|
||||
private DefaultEnvironment? _environment;
|
||||
private TestSystem? _testSystem;
|
||||
private TestModel? _testModel;
|
||||
private TestUtility? _testUtility;
|
||||
private TestTraditionalCommand? _testCommand;
|
||||
private TestTraditionalQuery? _testQuery;
|
||||
|
||||
[Test]
|
||||
public async Task SendRequestAsync_Should_ReturnResult_When_Request_IsValid()
|
||||
@ -200,32 +168,6 @@ public class MediatorComprehensiveTests
|
||||
Assert.That(TestNotificationHandler.LastReceivedMessage, Is.EqualTo("test event"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Mediator_And_CommandExecutor_Should_Coexist()
|
||||
{
|
||||
// 使用传统方式(Legacy)
|
||||
_context!.SendCommand(_testCommand!);
|
||||
Assert.That(_testCommand!.Executed, Is.True);
|
||||
|
||||
// 使用 Mediator 方式
|
||||
var mediatorCommand = new TestCommandWithResult { ResultValue = 123 };
|
||||
var result = await _context.SendAsync(mediatorCommand);
|
||||
Assert.That(result, Is.EqualTo(123));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Mediator_And_QueryExecutor_Should_Coexist()
|
||||
{
|
||||
// 使用传统方式(Legacy)
|
||||
var traditionalResult = _context!.SendQuery(_testQuery!);
|
||||
Assert.That(traditionalResult, Is.EqualTo(999));
|
||||
|
||||
// 使用 Mediator 方式
|
||||
var mediatorQuery = new TestQuery { QueryResult = "mediator result" };
|
||||
var mediatorResult = await _context.QueryAsync(mediatorQuery);
|
||||
Assert.That(mediatorResult, Is.EqualTo("mediator result"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetService_Should_Use_Cache()
|
||||
{
|
||||
@ -237,19 +179,6 @@ public class MediatorComprehensiveTests
|
||||
Assert.That(secondResult, Is.SameAs(firstResult));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Architecture_Component_Getters_Should_Work()
|
||||
{
|
||||
var system = _context!.GetSystem<TestSystem>();
|
||||
var model = _context.GetModel<TestModel>();
|
||||
var utility = _context.GetUtility<TestUtility>();
|
||||
var environment = _context.GetEnvironment();
|
||||
|
||||
Assert.That(system, Is.SameAs(_testSystem));
|
||||
Assert.That(model, Is.SameAs(_testModel));
|
||||
Assert.That(utility, Is.SameAs(_testUtility));
|
||||
Assert.That(environment, Is.SameAs(_environment));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Unregistered_Mediator_Should_Throw_InvalidOperationException()
|
||||
@ -363,81 +292,4 @@ public sealed class TestStreamRequestHandler : IStreamRequestHandler<TestStreamR
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Test Classes - Legacy CQRS (旧实现)
|
||||
|
||||
public class TestSystem : ISystem
|
||||
{
|
||||
private IArchitectureContext _context = null!;
|
||||
public int Id { get; init; }
|
||||
|
||||
public void SetContext(IArchitectureContext context) => _context = context;
|
||||
public IArchitectureContext GetContext() => _context;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnArchitecturePhase(ArchitecturePhase phase)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TestModel : IModel
|
||||
{
|
||||
private IArchitectureContext _context = null!;
|
||||
public int Id { get; init; }
|
||||
|
||||
public void SetContext(IArchitectureContext context) => _context = context;
|
||||
public IArchitectureContext GetContext() => _context;
|
||||
|
||||
public void Init()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnArchitecturePhase(ArchitecturePhase phase)
|
||||
{
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class TestUtility : IUtility
|
||||
{
|
||||
private IArchitectureContext _context = null!;
|
||||
public int Id { get; init; }
|
||||
|
||||
public void SetContext(IArchitectureContext context) => _context = context;
|
||||
public IArchitectureContext GetContext() => _context;
|
||||
}
|
||||
|
||||
// ✅ 使用你框架的 ICommand
|
||||
public class TestTraditionalCommand : ICommand
|
||||
{
|
||||
private IArchitectureContext _context = null!;
|
||||
public bool Executed { get; private set; }
|
||||
|
||||
public void Execute() => Executed = true;
|
||||
public void SetContext(IArchitectureContext context) => _context = context;
|
||||
public IArchitectureContext GetContext() => _context;
|
||||
}
|
||||
|
||||
// ✅ 使用你框架的 IQuery
|
||||
public class TestTraditionalQuery : IQuery<int>
|
||||
{
|
||||
private IArchitectureContext _context = null!;
|
||||
public int Result { get; init; }
|
||||
|
||||
public int Do() => Result;
|
||||
public void SetContext(IArchitectureContext context) => _context = context;
|
||||
public IArchitectureContext GetContext() => _context;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -636,8 +636,13 @@ public abstract class Architecture(
|
||||
|
||||
// 为服务设置上下文
|
||||
Services.SetContext(_context);
|
||||
// 添加 Mediator
|
||||
Container.RegisterMediator(Configuration.Configurator);
|
||||
if (Configurator is null)
|
||||
{
|
||||
_logger.Debug("Mediator-based cqrs will not take effect without the service setter configured!");
|
||||
}
|
||||
|
||||
// 执行服务钩子
|
||||
Container.ExecuteServicesHook(Configurator);
|
||||
// === 用户 Init ===
|
||||
_logger.Debug("Calling user Init()");
|
||||
Init();
|
||||
@ -660,11 +665,23 @@ public abstract class Architecture(
|
||||
|
||||
/// <summary>
|
||||
/// 等待架构初始化完成(Ready 阶段)
|
||||
/// 如果架构已经处于就绪状态,则立即返回已完成的任务;
|
||||
/// 否则返回一个任务,该任务将在架构进入就绪状态时完成。
|
||||
/// </summary>
|
||||
/// <returns>表示等待操作的Task对象</returns>
|
||||
public Task WaitUntilReadyAsync()
|
||||
{
|
||||
return IsReady ? Task.CompletedTask : _readyTcs.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用于配置服务集合的委托
|
||||
/// 默认实现返回null,子类可以重写此属性以提供自定义配置逻辑
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// 一个可为空的Action委托,用于配置IServiceCollection实例
|
||||
/// </value>
|
||||
public virtual Action<IServiceCollection>? Configurator => null;
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -2,8 +2,6 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.Abstractions.properties;
|
||||
using GFramework.Core.logging;
|
||||
using Mediator;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace GFramework.Core.architecture;
|
||||
|
||||
@ -34,17 +32,4 @@ public sealed class ArchitectureConfiguration : IArchitectureConfiguration
|
||||
AllowLateRegistration = false,
|
||||
StrictPhaseValidation = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置Mediator配置委托
|
||||
/// 用于自定义Mediator框架的配置选项
|
||||
/// </summary>
|
||||
/// <returns>配置Mediator选项的委托函数,可为null</returns>
|
||||
public Action<MediatorOptions>? Configurator { get; set; } = options =>
|
||||
{
|
||||
options.Namespace = "GFramework.Core.Mediator";
|
||||
options.ServiceLifetime = ServiceLifetime.Singleton;
|
||||
options.GenerateTypesAsInternal = true;
|
||||
options.NotificationPublisherType = typeof(ForeachAwaitPublisher);
|
||||
};
|
||||
}
|
||||
@ -266,29 +266,12 @@ public class MicrosoftDiContainer(IServiceCollection? serviceCollection = null)
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册 Mediator(基于 Source Generator)
|
||||
/// 配置服务
|
||||
/// </summary>
|
||||
/// <param name="configurator">可选的配置委托</param>
|
||||
public void RegisterMediator(Action<MediatorOptions>? configurator = null)
|
||||
/// <param name="configurator">服务配置委托</param>
|
||||
public void ExecuteServicesHook(Action<IServiceCollection>? configurator = null)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
ThrowIfFrozen();
|
||||
|
||||
// 添加 Mediator
|
||||
Services.AddMediator(options =>
|
||||
{
|
||||
// 用户自定义配置
|
||||
configurator?.Invoke(options);
|
||||
});
|
||||
|
||||
_logger.Info("Mediator registered with Source Generator");
|
||||
}
|
||||
finally
|
||||
{
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
configurator?.Invoke(Services);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@ -0,0 +1,38 @@
|
||||
// <auto-generated>
|
||||
// Generated by the Mediator source generator.
|
||||
// </auto-generated>
|
||||
|
||||
namespace Mediator
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an assembly reference.
|
||||
/// This is used to specify the types or assemblies to scan for Mediator handlers.
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Mediator.SourceGenerator", "3.0.0.0")]
|
||||
public sealed class AssemblyReference
|
||||
{
|
||||
/// <summary>
|
||||
/// The assembly reference.
|
||||
/// </summary>
|
||||
public global::System.Reflection.Assembly Assembly { get; }
|
||||
|
||||
private AssemblyReference(global::System.Reflection.Assembly assembly)
|
||||
{
|
||||
Assembly = assembly;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="AssemblyReference" /> from the specified type.
|
||||
/// </summary>
|
||||
/// <param name="type">The type</param>
|
||||
/// <returns>A new instance of <see cref="AssemblyReference" /></returns>
|
||||
public static implicit operator AssemblyReference(global::System.Type type) => new AssemblyReference(type.Assembly);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="AssemblyReference" /> from the specified assembly.
|
||||
/// </summary>
|
||||
/// <param name="assembly">The assembly</param>
|
||||
/// <returns>A new instance of <see cref="AssemblyReference" /></returns>
|
||||
public static implicit operator AssemblyReference(global::System.Reflection.Assembly assembly) => new AssemblyReference(assembly);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,55 @@
|
||||
// <auto-generated>
|
||||
// Generated by the Mediator source generator.
|
||||
// </auto-generated>
|
||||
|
||||
namespace Mediator
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide options for the Mediator source generator.
|
||||
/// </summary>
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Mediator.SourceGenerator", "3.0.0.0")]
|
||||
public sealed class MediatorOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The namespace in which the Mediator implementation is generated.
|
||||
/// By default, the namespace is "Mediator".
|
||||
/// </summary>
|
||||
public string Namespace { get; set; } = "Mediator";
|
||||
|
||||
/// <summary>
|
||||
/// Wether or not generated types should be <c>internal</c> (they are public by default).
|
||||
/// </summary>
|
||||
public bool GenerateTypesAsInternal { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="global::Mediator.INotificationPublisher" /> type to use when publishing notifications.
|
||||
/// By default, the type is <see cref="global::Mediator.ForeachAwaitPublisher" />.
|
||||
/// </summary>
|
||||
public global::System.Type NotificationPublisherType { get; set; } = typeof(global::Mediator.ForeachAwaitPublisher);
|
||||
|
||||
/// <summary>
|
||||
/// The default lifetime of the services registered in the DI container by the Mediator source generator.
|
||||
/// By default, the lifetime is <see cref="global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton" />.
|
||||
/// </summary>
|
||||
public global::Microsoft.Extensions.DependencyInjection.ServiceLifetime ServiceLifetime { get; set; } =
|
||||
global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton;
|
||||
|
||||
/// <summary>
|
||||
/// The collection of assemblies to scan for Mediator handlers.
|
||||
/// By default, the collection is empty, in which case the source generator will scan all assemblies through references from the source generated project.
|
||||
/// </summary>
|
||||
public global::System.Collections.Generic.IReadOnlyList<global::Mediator.AssemblyReference> Assemblies { get; set; } = new global::Mediator.AssemblyReference[0];
|
||||
|
||||
/// <summary>
|
||||
/// The collection of types of pipeline behaviors to register in DI.
|
||||
/// When the type is an unconstructed generic type, the source generator will register all the constructed types of the generic type (open generics that is supported during AoT).
|
||||
/// </summary>
|
||||
public global::System.Collections.Generic.IReadOnlyList<global::System.Type> PipelineBehaviors { get; set; } = new global::System.Type[0];
|
||||
|
||||
/// <summary>
|
||||
/// The collection of types of streaming pipeline behaviors to register in DI.
|
||||
/// When the type is an unconstructed generic type, the source generator will register all the constructed types of the generic type (open generics that is supported during AoT).
|
||||
/// </summary>
|
||||
public global::System.Collections.Generic.IReadOnlyList<global::System.Type> StreamPipelineBehaviors { get; set; } = new global::System.Type[0];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
// <auto-generated>
|
||||
// Generated by the Mediator source generator.
|
||||
// </auto-generated>
|
||||
|
||||
namespace Mediator
|
||||
{
|
||||
/// <summary>
|
||||
/// Provide options for the Mediator source generator.
|
||||
/// </summary>
|
||||
[global::System.AttributeUsage(global::System.AttributeTargets.Assembly, AllowMultiple = false)]
|
||||
[global::System.CodeDom.Compiler.GeneratedCode("Mediator.SourceGenerator", "3.0.0.0")]
|
||||
public sealed class MediatorOptionsAttribute : global::System.Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// The namespace in which the Mediator implementation is generated.
|
||||
/// By default, the namespace is "Mediator".
|
||||
/// </summary>
|
||||
public string Namespace { get; set; } = "Mediator";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="global::Mediator.INotificationPublisher" /> type to use when publishing notifications.
|
||||
/// By default, the type is <see cref="global::Mediator.ForeachAwaitPublisher" />.
|
||||
/// </summary>
|
||||
public global::System.Type NotificationPublisherType { get; set; } = typeof(global::Mediator.ForeachAwaitPublisher);
|
||||
|
||||
/// <summary>
|
||||
/// The default lifetime of the services registered in the DI container by the Mediator source generator.
|
||||
/// By default, the lifetime is <see cref="global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton" />.
|
||||
/// </summary>
|
||||
public global::Microsoft.Extensions.DependencyInjection.ServiceLifetime ServiceLifetime { get; set; } =
|
||||
global::Microsoft.Extensions.DependencyInjection.ServiceLifetime.Singleton;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user