Pitfalls of Service Locator
Service Locator is quite a widespread design pattern. You can find the usage of Microsoft.Practices.ServiceLocation.ServiceLocator in many modern third-party software components. Under certain circumstances, it may cause quite interesting issues and I want to share with you one of such problems I have recently faced.
We have a solution that uses InSite assemblies from a Sitecore partner almost on each page of our website. My task was to replace current search functionality and integrate SolrNet into it. After the implementation was done, code was tested and deployed to our test environment for further testing. In a day, we got a bug report describing that suddenly many pages in the test environment were falling with the “The given key was not present in the dictionary.” error message and the stack trace looked like the following:
The strange thing was that after half an hour the issue disappeared and then reappeared the day after tomorrow and again disappeared after some time. I was quite confused by this behavior and started my investigation by checking different aspects of the solution and what had been changed during the last few days.
After some time I had a reproducing scenario:
start the website -> go to any available page: make sure it works fine -> go to the search page and perform some search -> return to the previous page: it fails with the above-mentioned exception-> restart the site and the issue disappears.
What was the reason
So, before you can start using SolrNet, you need to initialize it in order to allow SolrNet to register all its dependencies:
Now, if we take a decompiler (JustDecompile in my case) and look into code inside of the Startup class – we will see that it has a static constructor registering all necessary dependencies and setting its own ServiceLocatorProvider for ServiceLocator:
Unfortunately, InSite uses ServiceLocator too and sets its own ServiceLocatorProvider. So, when the site starts running, InSite sets its ServiceLocatorProvider; when we go to the search page and perform the search, SolrNet replaces InSite’s ServiceLocator with its own one. Afterwards, InSite cannot work properly and start throwing exceptions on every page. Looks like these assemblies cannot work together “by default”.
In order to solve the issue, I examined what dependencies SolrNet had and used. I created a wrapper around SolrNet that took additional optional parameter in its constructor, which indicated whether we need to use standard workflow or my helper initializer:
The Init method of the UnityStartupInitializer class simply takes the same locator that is used by Insite from ServiceLocator.Current and if it is not null – registers all SolrNet interfaces there.
The solution isn’t as elegant as I wanted it to be but it does its job well and it is quite hard to find another approach without having access to the source files of SolrNet and InSite.
In the conclusion, I want to highlight a few points:
- Always closely investigate an exception’s stack trace.
- If you have ServiceLocator in a stack trace, make sure it is initialized with the same instance at your application start and before your exception.
- Don’t be afraid to use decompiled tools (like Reflector or JustDecompile) and dive into code of your assemblies. Believe me, it will help you to solve many problems and improve your knowledge.
Hope this will save someone’s time.