ASP.NET Core MVC事件到视图传值的临时存储方案咨询
ASP.NET Core MVC中第三方库回调结果传递的方案分析
问题背景
我正在开发一个ASP.NET Core MVC Web应用,领域对象方法会调用第三方库的方法,该方法执行出错时会触发回调函数。我的目标是把回调函数的结果传递给服务,再由服务传给控制器和视图,目前实现了两种方案:
- 方案1:将结果临时存储在自定义事件处理程序的成员变量中
- 方案2:将结果临时存储在.NET Core的HttpContext.Items中
两种方案都能运行,但方案2导致应用运行极慢,感觉HttpContext在这个场景下太冗余了。想请教三个问题:
- 我是不是误用了HttpContext?
- 方案1类似带公共成员变量的JavaScript风格事件处理程序,目前没发现性能问题,这个方案有哪些弊端?
- 有没有其他推荐方案?
方案1实现代码
ServiceA类
private IEventBus _eventBus; private MyEventHandler _eventHandler; public ServiceA(IEventBus eventBus, MyEventHandler eventHandler) { _eventBus = eventBus; _eventHandler = eventHandler; } public MyDTO DoSthA(string arg) { DomainA domainObj = new DomainA(_eventBus); domainObj.doSth(arg); MyDTO ret = new MyDTO(); ret.EventResult = _eventHandler.Result; return ret; }
DomainA类
private readonly IEventBus _eventBus; private readonly Some3rdPartyStuff _some3rdPartyStuff; public DomainA(IEventBus eventBus) { _eventBus = eventBus; _some3rdPartyStuff = new Some3rdPartyStuff(); // 配置第三方库对象 _some3rdPartyStuff.OnSomeEvent(() => { var @event = new MyEvent(); // 设置事件成员变量 _eventBus.Dispatch(@event); }); } public void DoSth(string arg) { _some3rdPartyStuff.doSth(arg); }
事件总线(单例作用域)
public interface IEventBus { void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent; } public class MyEventBus : IEventBus { public void Dispatch<TEvent>(TEvent @event) where TEvent : IEvent { if (@event == null) throw new ArgumentNullException("event"); var handlerType = typeof(IEventHandler<>).MakeGenericType(@event.GetType()); dynamic handlers = DIContainer.INSTANCE.GetAllInstances<IEventHandler<TEvent>>(); foreach (var handler in handlers) { handler.Handle(@event); } } }
事件处理程序(请求作用域)
public interface IEventHandler<TEvent> { void Handle(TEvent @event); } public class MyEventHandler : IEventHandler<MyEvent> { public EventResult Result { get; private set; } public void Handle(MyEvent @event) { var result = new EventResult(); // 设置EventResult变量 Result = result; } }
方案2实现代码
ServiceA类
private IEventBus _eventBus; private IHttpContextAccessor _httpContextAccessor; public ServiceA(IEventBus eventBus, IHttpContextAccessor httpContextAccessor){ _eventBus = eventBus; _httpContextAccessor = httpContextAccessor; } public MyDTO DoSthA(string arg){ DomainA domainObj = new DomainA(_eventBus); domainObj.doSth(arg); bool eventHandled = _httpContextAccessor.HttpContext.Items.Contains("key"); MyDTO ret = new MyDTO(); ret.EventResult = eventHandled ? _httpContextAccessor.HttpContext.Items["key"] : new MyEventResult(); // 默认结果值 return ret; }
事件处理程序(请求作用域)
public interface IEventHandler<TEvent> { void Handle(TEvent @event); } public class MyEventHandler : IEventHandler<MyEvent> { private IHttpContextAccessor _httpContextAccessor; public MyEventHandler(IHttpContextAccessor httpContextAccessor){ _httpContextAccessor = httpContextAccessor; } public void Handle(MyEvent @event) { var result = new EventResult(); // 设置EventResult变量 _httpContextAccessor.HttpContext.Items["key"]= result; } }
问题解答
1. 是否误用了HttpContext?
是的,你确实误用了HttpContext。HttpContext.Items设计初衷是在同一个请求的不同组件间临时共享请求相关数据,但它并非为这种同步的、局部的事件结果传递场景而生。HttpContext本身包含大量请求上下文信息,访问Items会涉及线程安全检查、上下文切换等额外开销,高频调用时会显著拖慢性能。更关键的是,这种方式将业务逻辑与Web上下文强耦合,一旦服务脱离Web环境(比如改成控制台应用、后台任务),代码直接失效。
2. 方案1的弊端?
方案1虽暂无性能问题,但存在以下关键隐患:
- 线程安全风险:若服务扩展为多线程并行处理,或事件处理改为异步,多个线程同时访问/修改
Result属性会引发数据竞争,导致不可预期的结果。 - 状态污染:MyEventHandler是请求作用域对象,若忘记重置
Result,下一次请求可能拿到上一次请求的残留数据,引发逻辑错误。 - 违反单一职责:事件处理程序的核心职责是处理事件,存储结果属于额外职责,不符合SOLID原则,代码扩展性差。
- 可测试性差:单元测试时需模拟EventBus和MyEventHandler,还要处理成员变量的状态重置,容易出现测试用例互相干扰的情况。
3. 推荐方案
方案一:事件自带结果容器
修改事件定义,让事件本身携带结果存储容器,事件处理程序直接写入容器,服务调用后从容器获取结果:
// 带结果容器的事件 public class MyEvent : IEvent { public EventResult ResultContainer { get; set; } = new EventResult(); } // DomainA中的事件触发逻辑 _some3rdPartyStuff.OnSomeEvent(() => { var @event = new MyEvent(); // 设置事件其他变量 _eventBus.Dispatch(@event); }); // MyEventHandler处理逻辑 public void Handle(MyEvent @event) { @event.ResultContainer.Status = "Handled"; // 其他结果赋值 } // ServiceA中获取结果 public MyDTO DoSthA(string arg) { var myEvent = new MyEvent(); var domainObj = new DomainA(_eventBus); domainObj.DoSth(arg, myEvent); var ret = new MyDTO(); ret.EventResult = myEvent.ResultContainer; return ret; }
该方式结果传递直接,无额外状态存储,线程安全且职责清晰。
方案二:异步回调+TaskCompletionSource
若第三方库支持异步,或可包装为异步操作,用TaskCompletionSource等待回调结果:
// DomainA中包装第三方调用为异步方法 public async Task<EventResult> DoSthAsync(string arg) { var tcs = new TaskCompletionSource<EventResult>(); _some3rdPartyStuff.OnSomeEvent(() => { var result = new EventResult(); // 设置结果 tcs.SetResult(result); }); try { _some3rdPartyStuff.doSth(arg); return await tcs.Task; } catch (Exception ex) { tcs.SetException(ex); throw; } } // ServiceA中调用 public async Task<MyDTO> DoSthAAsync(string arg) { var domainObj = new DomainA(_eventBus); var eventResult = await domainObj.DoSthAsync(arg); var ret = new MyDTO(); ret.EventResult = eventResult; return ret; }
适合异步场景,代码逻辑线性,可测试性强。
方案三:自定义请求作用域结果存储服务
创建专门的请求作用域服务存储事件结果,替代方案1的成员变量:
// 结果存储服务接口 public interface IEventResultStore { EventResult CurrentResult { get; set; } void Reset(); } // 结果存储服务实现 public class EventResultStore : IEventResultStore { public EventResult CurrentResult { get; set; } = new EventResult(); public void Reset() { CurrentResult = new EventResult(); } } // DI中注册为请求作用域 services.AddScoped<IEventResultStore, EventResultStore>(); // MyEventHandler注入结果存储服务 public class MyEventHandler : IEventHandler<MyEvent> { private readonly IEventResultStore _resultStore; public MyEventHandler(IEventResultStore resultStore) { _resultStore = resultStore; } public void Handle(MyEvent @event) { var result = new EventResult(); // 设置结果 _resultStore.CurrentResult = result; } } // ServiceA中使用 public class ServiceA { private readonly IEventBus _eventBus; private readonly IEventResultStore _resultStore; public ServiceA(IEventBus eventBus, IEventResultStore resultStore) { _eventBus = eventBus; _resultStore = resultStore; } public MyDTO DoSthA(string arg) { _resultStore.Reset(); var domainObj = new DomainA(_eventBus); domainObj.DoSth(arg); var ret = new MyDTO(); ret.EventResult = _resultStore.CurrentResult; return ret; } }
该方案将状态存储职责抽离,符合单一职责原则,避免状态污染,扩展性和可测试性更强。
内容的提问来源于stack exchange,提问作者anndexi99
相关产品推荐
相关产品推荐

