The Umbraco APIs are full of examples where the developers have made tough design choices. The
UmbracoHelper is the one, from my own experience at least, in which the reasoning behind the design is not immediately obvious.
In many cases, the way the
UmbracoHelper was designed is a blessing - enabling seamless use in Razor views and providing convenient self-documentation to the most important Umbraco APIs for heavy IntelliSense users. However, once you start using
UmbracoHelper in your own controllers and services, and then start trying to write unit tests for that code and using dependency injection, you may be tempted to view reliance on this ubiquitous static class as a hinderance. The way to decouple your code from this dependency is hard to find through IntelliSense or through documentation.
But don't despair just yet, there is a little-known trick which makes unit testing code that uses the
UmbracoHelper APIs both convenient and laughably easy - without endless fiddling trying mock
UmbracoContext in your test fixtures.
And there's no need spend your precious time trying to re-implement
UmbracoHelper in your own services, especially because
UmbracoHelper was written by people who know what they are doing, and hidden in those method calls are often many layers of caching which are hard to get right unless you know Umbraco inside-out.
The trick: UmbracoHelper uses Interface Segregation
My pain with
UmbracoHelper came from my expectation that there would be an
IUmbracoHelper interface, which I could depend on in my controllers and services and easily mock in my tests.
But what I didn't know is that since 2014 there's been a ton of interfaces which themselves come together in the full
UmbracoHelper. Using one interface for the entire class wouldn't be ideal, because there are just so many methods in there, but instead your code can just depend on the interfaces which it actually uses, enabling you to mock out those dependencies with well-known techniques and mocking libraries. Here they are in full:
Now this isn't interface segregation by any strict definition, because the class
UmbracoHelper doesn't inherit from these directly. Rather,
UmbracoHelper exposes these bits of it's API as object properties. Here follows a code example which will make the use of this technique a little clearer.
Writing decoupled code which uses UmbracoHelper
Here's a code snippet from one of my projects. It uses
TypedContentAtXPath, a method from
UmbracoHelper, to get every content node with a specific document type, and then uses Ditto to convert those
IPublishedContent objects into my own model POCOs.
The special thing about this code is that it doesn't even know
UmbracoHelper exists. It depends instead on that interface
ITypedPublishedContentQuery which defines the bit of the API that it actually uses.
Now, how easy is it to instantiate this class, passing in the required dependency? As I've said above - laughably easy. Here's the relevant line from the AutoFac startup configuration for this same project.
UmbracoHelper just exposes, as properties, classes which inherit from these interfaces - in this case
If you're not familiar with AutoFac, that's fine, it's not necessary. AutoFac just does the job of passing dependencies into my constructors for me.
You could just as easily instantiate your this same class like this:
After trying unsuccessfully to decouple code from the UmbracoHelper in a graceful way in many projects, this discovery was a real "eureka" moment for me. According to Shannon, not all features of the API are yet available through these interfaces, but most of the work I'm doing with
UmbracoHelper was available with the APIs provided in
ITypedPublishedContentQuery, making this interface become a close friend to my Umbraco codebases.
One more caveat is the necessity to refactor your code to depend on these interfaces, instead of
UmbracoHelper directly or your own home-grown interfaces - but hey - who doesn't like refactoring?
If you've found this article useful, or if you have any feedback, I invite you to get in touch with me on twitter @glcheetham. Thanks for the your time and I hope you enjoyed the read :-)