A call to Umbraco.Content leads to an exception:
[ObjectDisposedException: Cannot access a disposed object.
Object name: 'snapshot'.]
Umbraco.Web.PublishedCache.NuCache.Snapshot.Get(Int32 id) in C:\Projekte\Umbraco-CMS\src\Umbraco.Web\PublishedCache\NuCache\ContentStore.cs:1131
Umbraco.Web.PublishedCache.NuCache.ContentCache.GetById(Boolean preview, Int32 contentId) in C:\Projekte\Umbraco-CMS\src\Umbraco.Web\PublishedCache\NuCache\ContentCache.cs:231
Umbraco.Web.PublishedCache.PublishedCacheBase.GetById(Int32 contentId) in C:\Projekte\Umbraco-CMS\src\Umbraco.Web\PublishedCache\PublishedCacheBase.cs:23
Umbraco.Web.PublishedContentQuery.ItemById(Int32 id, IPublishedCache cache) in C:\Projekte\Umbraco-CMS\src\Umbraco.Web\PublishedContentQuery.cs:123
Umbraco.Web.PublishedContentQuery.Content(Int32 id) in C:\Projekte\Umbraco-CMS\src\Umbraco.Web\PublishedContentQuery.cs:37
Umbraco.Web.UmbracoHelper.Content(Int32 id) in C:\Projekte\Umbraco-CMS\src\Umbraco.Web\UmbracoHelper.cs:326
Unfortunately, I can't create a simple System that helps reproducing the Bug. The Code in my System comes down to the following Steps:
In my master layout Master.cshmtl I call a Method in a helper class like that:
<meta name="description" [email protected]()>
The GetDescription() Method in the helper class determines a content like that:
pc = Umbraco.Content( (int) HttpContext.Current.Items["CurrentPage"] );
In the specified case the parameter to Umbraco.Content is exactly the Id of the content, which is about to be displayed using the master.cshtml. So it's definitely published and it should be possible to render the content.
The exception occurs in Snapshot.Get(int id) in ContentStore.cs:
public ContentNode Get(int id)
{
if (_gen < 0)
throw new ObjectDisposedException("snapshot" /*+ " (" + _thisCount + ")"*/);
return _store.Get(id, _gen);
}
The exception is thrown because _gen < 0.
Umbraco.Content should return the IPublishedContent with the given Id
The exception is thrown.
What is MyHelperClass doing? This normally will happen if you are referencing a stale scope which normally happens when developers assign static references to things that are scoped to web requests.
MyHelperClass.GetDescription() checks, if certain elements are in the HttpContext.Items cache (we plan to replace that with the Umbraco RequestCache in the future) and if an Element "CurrentPage" exists, it executes the line, which throws the exception:
pc = Umbraco.Content( (int) HttpContext.Current.Items["CurrentPage"] );
the 'CurrentPage' item is an integer id of a published page.
I replaced Umbraco.Web.dll with debug compilations (release-8.0.0 tag) and you can see the call stack with code lines in the error message above.
Note: The first time I show the page after an application restart, the code works. The second time (e.g. pressing F5) it fails.
Aaaahhh, thanks a lot for your answer. It made me think about, what "Umbraco" means in the given context. It was a static copy of Current.UmbracoHelper:
static UmbracoHelper umbracoHelper;
static UmbracoHelper Umbraco => umbracoHelper ?? (umbracoHelper = Current.UmbracoHelper);
Changing it to
static UmbracoHelper Umbraco => Current.UmbracoHelper;
helped…
Thanks again for your fast response!
I very highly recommend reading this document https://our.umbraco.com/documentation/Reference/Common-Pitfalls/. You are not the first person to do this and this circumstance also exists in v7 but doesn't error out but instead will cause your app to behave in odd ways or throw null ref exceptions.
I also highly recommend to stop using statics and stop using singletons. These are the cause for so many issues and also for spaghetti code. If you require a custom helper in your views, best to create a new helper with it's dependencies specified in the constructor when you need it. Or, If you require your custom helper in your controllers, then it's best to register your helper in dependency injection container and add this dependency to your controllers constructor.
Thanks for your useful hints. Actually it was a moment of not thinking, which caused this code example. I should have known, that this will cause trouble.
I agree 100% with your recommendation to avoid statics. But we are about porting an existing solution to V8. And we can't change every piece of code at once. Unfortunately there are quite some extension classes in the project. In my opinion extension classes are an anti-pattern in a solution with IoC. But converting existing extension classes to IoC needs a lot of time.
It would be very useful (and might save you some time) to complement the Common-Pitfalls document with a strategy and examples of howto convert static classes or singletons to instances with IoC constructors. I'm one of the first stumbling over this questions, but a few 1000 of developers will follow and stumbling over the same questions ;-). And it would be a good idea to nail the Common-Pitfalls document somewhere at the top of the forum in our-umbraco.
Thanks again for your answers.
Indeed, and we need to find time to get all the v8 bits and pieces documented. It's in the works but it's slow going currently but we'll get there.
Yup extension methods generally are good culprits for using singletons because people don't want to bother with passing in all of the dependencies the method needs. That said, in some cases maybe this is warranted and in some cases we might even do this in the Core if the syntactic sugar is worth the risk. But I'd always suggest passing in all dependencies to ext methods first, figure out if maybe an actual service class is needed for that (in most cases yes) and go from there :)