C# 在非Web代码中模拟请求范围
背景:我需要系统的某些部分能够将各种状态消息推送到某些数据结构中,以便调用者可以使用它们,而无需将数据结构显式地传递到方法中,并且调用者的需求可能不同 详细信息:我的应用程序有两个(可能更多)头,一个ASP.NET MVC 5网站和一个Windows服务。因此,通常情况下,虽然web应用程序的合成根是网站本身,但我使用的是一个单独的合成根,这两个“前端”都连接到它——这允许它们共享一个公共配置,因为它们的依赖项注入几乎都是100%相同的。另外,对于测试,我决定将大部分代码保留在网站之外,因为真正的单元测试控制器是有问题的 因此,我的代码需要能够在任何web请求的上下文之外运行。类似地,服务按计划执行的任何操作都需要能够作为网站上的按需作业运行。因此,我的应用程序中大部分繁重的代码都不在网站或服务中 现在,回到我的状态消息的需要:C# 在非Web代码中模拟请求范围,c#,asp.net-mvc-5,static,singleton,C#,Asp.net Mvc 5,Static,Singleton,背景:我需要系统的某些部分能够将各种状态消息推送到某些数据结构中,以便调用者可以使用它们,而无需将数据结构显式地传递到方法中,并且调用者的需求可能不同 详细信息:我的应用程序有两个(可能更多)头,一个ASP.NET MVC 5网站和一个Windows服务。因此,通常情况下,虽然web应用程序的合成根是网站本身,但我使用的是一个单独的合成根,这两个“前端”都连接到它——这允许它们共享一个公共配置,因为它们的依赖项注入几乎都是100%相同的。另外,对于测试,我决定将大部分代码保留在网站之外,因为真正
public IReadOnlyCollection<UsableItem> GetUsableItems(
ReadOnlyHashSet<string> itemIds,
List<StatusMessage> statusMessages
) {
var resultItems = _itemService.Get(itemIds);
var resultItemsByHasFrobDuplicate = resultItems
.GroupBy(i => i.FrobId)
.ToLookup(grp => grp.Count() > 1, grp => grp.ToList());
statusMessages
.AddRange(
resultItemsByHasFrobDuplicate[true]
.Select(items => $@"{items[0].FrobId
} is used by multiple items {string.Join(",", items.Select(i => i.usableItemId))
}")
);
return resultItemsByHasFrobDuplicate[false]
.Select(grp => grp.First())
.ToList()
.AsReadOnly();
}
啊。这并不理想
我立刻想到了与之类似的MiniProfiler.Current
,它在任何地方都可用,但范围仅限于当前请求。但我不明白它怎么能是静态的,却能在不同的请求之间隔离任何Step
调用,这样用户就不会在输出中得到另一个用户的步骤。另外,它不是只适用于MVC吗?我需要在没有MVC,只有非web代码的情况下使用它
有人能提出一种改进我的代码的方法,而不必一个接一个地将列表传递给一个方法吗?与单元测试一起工作的东西也很重要,因为我需要能够设置一种方法,在单元测试中捕获模拟中冒泡的错误(或者如果这不是要测试的系统的期望部分,那么就什么都不做)
另外,我不介意对我上面的
ToLookup
模式进行委婉的批评,因为它分离了重复项。我经常使用这种技术,并且会对更好的方法感兴趣。我想你只是看错了。所有这些实际上都不涉及请求,也不与请求相关。您只需要一些可以注入的服务就可以将消息推出。它是如何做到这一点的并不重要,依赖项注入的全部要点是,具有依赖项的类不应该知道或关心
为您的消息服务创建接口:
public interface IMessagingService
{
void PushMessage(string message);
}
然后,您应该稍微修改包含GetUsableItems
的类,以便将消息传递服务注入构造函数。通常,不赞成方法注入(您当前通过将List
传递到方法中所做的事情)
public class MyAwesomeClass
{
protected readonly IMessagingService messenger;
public MyAwesomeClass(IMessagingService messenger)
{
this.messenger = messenger;
}
然后,在您的方法中:
messenger.PushMessage("My awesome message");
然后,该接口的实现可能会因其是注入web应用程序还是windows服务而有所不同。您的web应用可能会有一个只利用自己的代码推送消息的实现,而windows服务可能需要一个利用
HttpClient
向您的web应用发出请求的实现。设置DI容器,为正确的应用程序注入正确的实现,就完成了。这似乎是合理的,至少让我可以将传递的对象移动到方法的包含类,尽管我可能需要将一些依赖项从静态范围移动到请求范围(不是什么大问题)。然而,我的项目有几个部分,这意味着我有相当多的类可以注入服务。我同意这比我所拥有的要好,我可能会这么做(这里有点像拍脑袋的时刻),但我仍然喜欢用一种不是真正全球化的“全球化”方式。哦,是的,我只是本能地觉得方法注入很糟糕,但我只是没有看到更好的方式。既然你这么说了,很明显。。。我没有人给我一个实质性的代码审查在这里。。。唉,在应用程序的上下文中,您这里的服务可能是“全局的”。您将向它注入一个单例/请求范围。该服务将担心如何持久化您的消息,以便所有应用程序都可以使用这些消息。
messenger.PushMessage("My awesome message");