Reputation: 341
I have a class that stores information about the context of current action. For example, every action belongs to some action block, so for logging(and many other) purposes I pass around the instance of that class that stores which action block it is:
ExecuteAction(action,context);
That lets me log things like the following:
var logString = $"action{action.Name} was executed as part of {context.ActionBlockName} action block";
Being able to get current action block is very important, as same actions get performed within different action blocks, and there can be any amount of active contexts at any given time.
However, my code is pretty complex with thousands of method calls layered on each other and I often have to form log strings deep inside 5-6 different method calls, meaning that this context
simply gets passed around too much and is inside of majority of method signatures.
I was thinking about writing all context information to file or database for all methods to access, but they would still have to find current context among all active ones. That means I will have to pass around some kind of context key, which isn't better than passing context itself.
Is there a better way to do this?
Ideally, the result I need is being able to call ExecuteAction(action);
without storing any context info in action
, while being able to somehow figure out what context is inside ExecuteAction
Upvotes: 0
Views: 197
Reputation: 33851
There are a couple approaches that come to mind:
1) Dependency Injection: It's popular in the last several years to use a Dependency Injection Container to help with this. The idea is create your context object and then register an object with the DI container to gain access to it, perhaps something like CurrentContextAccessor which has a GetCurrent method on it. That method would house the logic to know which context object is the "current one". Then all your objects that need access to this context would be registered with the DI container and would have the CurrentContextAccessor object specified as a constructor parameter so that when you obtain such an object from the DI container it automatically has the CurrentContextAccessor object injected into it via the constructor so that it has access to the current context.
ASP.NET Core is an example of a system built on this approach. One of the pros to this approach is that only your object's constructor method needs to have the CurrentContextAccessor passed in, non of it's methods need it since they have access to the context internally through a member variable where the constructor stored the current context. And you don't have to specifically pass the CurrentContextAccessor to any object that you register with the DI container since it will automatically get injected into that object when the object is requested from the DI container. Another pro of this approach is that it leads to loosely coupled code that should be easily unit testable. One of the cons of this approach is that DI containers and using them in this way can create a learning curve for developers needing to maintain the code, and some would argue that it makes the flow of control in the program harder to follow.
2) Global Static Accessor: You could create a create a static method hanging off of some globally accessible class that provides access to the current context. This has the advantage of being simpler to implement than the first approach and easier for junior developers to follow the flow of control in the code. But it tightly couples all your classes to the is global static variable and this makes it harder to reuse your classes in other project and makes it very difficult to run unit tests on the code that uses the global static context accessor. ASP.NET Full framework makes available HttpContext.Current
which is an example of this approach.
There are also other approaches like the Service Locator Pattern (which you can google) but it often still uses a DI container (although it doesn't have to) and still makes the objects hard to unit test. So you get the cons from both approaches listed above unless you come up with a really cleaver implementation.
Upvotes: 1