2017-04-12 34 views
0

背景:我需要我系统的某些部分能够将各种状态消息推送到某个数据结构,以便它们可以被调用方使用,而无需将数据结构传递到方法中明确地说,以及呼叫者的需求可以不同的地方。在非Web代码中模拟请求范围

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

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

现在,回到我的状态信息的需求:

  1. 某些状态信息将被记录,但有可能在作为服务运行更将被记录。排队日志项并将其保存在最后可以。
  2. 当网站按需运行作业时,可能会记录的东西更少,因为用户可以处理的任何问题都会直接显示给用户,而为了进行调试,我们只关心直接发生错误。新消息需要立即推送到网站(可能通过websockets)。
  3. 此外,作业可以在调试模式或详细模式下运行,以便一次(比如在网络上)产生比另一次(来自无头服务)更多的信息或警告消息。代码生成消息根本不应该担心这些细节,除非那些会影响生产性能的东西放在编译器指令的调试模式中)。
  4. 此外,某些代码会将错误,警告或信息推送到从请求返回的对象中。这些很容易处理。但是其他错误,警告或信息(例如阻止所有请求的对象被取回的错误)需要在正常返回值之外冒泡。

现在我正在使用一些看起来不太理想的东西:我所有的方法都必须接受一个参数,他们可以修改这些参数来消除这些错误。例如:

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 => [email protected]"{items[0].FrobId 
       } is used by multiple items {string.Join(",", items.Select(i => i.usableItemId)) 
       }") 
     ); 
    return resultItemsByHasFrobDuplicate[false] 
     .Select(grp => grp.First()) 
     .ToList() 
     .AsReadOnly(); 
} 

所以,你可以在这里看到,虽然一般项目可以在返回值从方法(和这些项目甚至能放在他们自己的状态信息),有些则不能,调用代码无法处理重复项,并且期望收集的UsableItem对象没有重复的FrobId值。重复的情况是意想不到的,需要冒泡到用户或日志。

的代码将通过能够去除statusMessages参数,并做更多的东西一样CurrentScope.PushMessage(message),知道这些信息会得到妥善处理基于其严重程度或其他规则大大提高(真正的消息是有几个属性的对象)。

哦,我在上面的代码中留下了一些东西。我真正要做的是:

_itemService.Get(itemIds, statusMessages); // -- take the darn parameter everywhere 

哎呀。这并不理想。

我立刻想到了MiniProfiler.Current类似,它是可在任何地方,但它限制在当前的请求。但我不明白它是如何能够是静态的,但隔离不同请求之间的任何Step呼叫,使得用户没有得到他的输出另一个用户的步骤。另外,它不只适用于MVC?我需要这个工作,当没有MVC,只是非网络代码。

任何人都可以提出一个方法来改善我的代码,而不必绕列表传递给方法方法之后?东西,将单元测试工作也很重要,因为我需要能够建立捕获单元测试中在我的模拟鼓入的错误的手段(或能够做什么都没有,如果这不是想要的部分要测试的系统)。

P.S.我不介意对我上面的小ToLookup模式进行委婉的批评以区分重复。我使用这种技术很多,并会对更好的方式感兴趣。

回答

0

我认为你只是看着这个走错了路。这实际上并不涉及或实际上与请求有关。您只需要一些可以将消息推出的服务。 它是如何这是否是无关紧要的,和依赖注入的整点是与依赖的类不应该知道或关心。

为您的消息服务创建的接口:

public interface IMessagingService 
{ 
    void PushMessage(string message); 
} 

然后,你应该改变你的类,它包含GetUsableItems有点注入消息服务到构造。一般来说,方法注入(通过将List<StatusMessages>传入方法,目前正在进行的操作)被忽视了。

public class MyAwesomeClass 
{ 
    protected readonly IMessagingService messenger; 

    public MyAwesomeClass(IMessagingService messenger) 
    { 
     this.messenger = messenger; 
    } 

然后,在你的方法:

messenger.PushMessage("My awesome message"); 

此接口的实现,那么可能会有所不同根据无论是在Web应用程序或Windows服务注入。您的网络应用程序可能会有一个实现,它只是简单地使用自己的代码来推送消息,而Windows服务可能需要使用HttpClient向您的Web应用程序发出请求的实现。设置您的DI容器,为正确的应用程序注入正确的实现,然后完成。

+0

这似乎是合理的,并且至少可以让我将对象传递移动到我的方法的包含类,尽管我可能需要将一些依赖关系从静态作用域移到请求作用域(不是什么大问题)。然而,我的项目有几个部分和部分,这意味着我有相当数量的类来注入服务。我认为这比我所拥有的要好,我可能会这样做(这里是一个头脑清醒的时刻),但我仍然喜欢某种方式来使用并非真正全球化的“全球化”。 – ErikE

+0

哦,是的,我只是本能地觉得方法注入很糟糕,但我只是没有看到更好的方法。现在你说出来了,这很明显......我没有人在这里给我一个实质性的代码审查......感叹。 – ErikE

+0

那么,您在这里的服务可能是应用程序环境中的“全局”。你可以用Singleton/Request作用域注入它。该服务将担心如何保持消息的某些方式,以便它们可供所有应用程序使用。 –