C# 在非Web代码中模拟请求范围

C# 在非Web代码中模拟请求范围,c#,asp.net-mvc-5,static,singleton,C#,Asp.net Mvc 5,Static,Singleton,背景:我需要系统的某些部分能够将各种状态消息推送到某些数据结构中,以便调用者可以使用它们,而无需将数据结构显式地传递到方法中,并且调用者的需求可能不同 详细信息:我的应用程序有两个(可能更多)头,一个ASP.NET MVC 5网站和一个Windows服务。因此,通常情况下,虽然web应用程序的合成根是网站本身,但我使用的是一个单独的合成根,这两个“前端”都连接到它——这允许它们共享一个公共配置,因为它们的依赖项注入几乎都是100%相同的。另外,对于测试,我决定将大部分代码保留在网站之外,因为真正

背景:我需要系统的某些部分能够将各种状态消息推送到某些数据结构中,以便调用者可以使用它们,而无需将数据结构显式地传递到方法中,并且调用者的需求可能不同

详细信息:我的应用程序有两个(可能更多)头,一个ASP.NET MVC 5网站和一个Windows服务。因此,通常情况下,虽然web应用程序的合成根是网站本身,但我使用的是一个单独的合成根,这两个“前端”都连接到它——这允许它们共享一个公共配置,因为它们的依赖项注入几乎都是100%相同的。另外,对于测试,我决定将大部分代码保留在网站之外,因为真正的单元测试控制器是有问题的

因此,我的代码需要能够在任何web请求的上下文之外运行。类似地,服务按计划执行的任何操作都需要能够作为网站上的按需作业运行。因此,我的应用程序中大部分繁重的代码都不在网站或服务中

现在,回到我的状态消息的需要:

  • 将记录一些状态消息,但作为服务运行时可能会记录更多状态消息。可以对日志项进行排队,并在最后保存它们
  • 比如说,当从网站按需运行作业时,记录的内容可能会更少,因为用户可以处理的任何问题都将直接显示给用户,出于调试目的,我们只关心发生的直接错误。新消息需要立即推送到web站点(可能通过WebSocket)
  • 此外,作业可以在调试或详细模式下运行,因此一次(比如在web上)产生的信息或警告消息比另一次(来自headless服务)产生的信息或警告消息更多。生成消息的代码不应该担心这些细节,除非在调试模式的编译器指令中放置了会影响生产性能的内容
  • 此外,一些代码将错误、警告或信息推送到从请求返回的对象中。这些很容易处理。但是,其他错误、警告或信息(例如阻止提取所请求对象的错误)需要在正常返回值之外冒泡
  • 现在,我使用的东西似乎不太理想:我的所有方法都必须接受一个参数,它们可以修改这个参数,以便冒泡出这样的错误。例如:

    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");