I'm attempting to create content, then save and publish using the content service but I'm running in to a null reference exception.
I'm not sure why this is happening, my hunch at the moment it is that I'm trying to create it from a composer and wondering if maybe something isn't initialised for me at that point.
Anyway, here's the code I'm using (or at least, the important parts):
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Models;
namespace Test
{
[RuntimeLevel(MinLevel = RuntimeLevel.Run)]
public class ContentComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Components().Append<ContentComponent>();
}
}
public class ContentComponent : IComponent
{
public void Initialize()
{
IContent homepageNode = Current.Services.ContentService.GetRootContent().FirstOrDefault(x => x.ContentType.Alias == "homepage");
if (homepageNode == null)
{
IContent homepage = Current.Services.ContentService.Create("Homepage", -1, "homepage");
Current.Services.ContentService.SaveAndPublish(homepage);
}
}
public void Terminate() { }
}
}
When I attempt to do this, I get the following error:
Object reference not set to an instance of an object.
And the stack trace:
at Umbraco.Web.Routing.RedirectTrackingComponent.get_Moving()
at Umbraco.Web.Routing.RedirectTrackingComponent.get_LockedEvents()
at Umbraco.Web.Routing.RedirectTrackingComponent.ContentService_Publishing(IContentService sender, PublishEventArgs`1 args)
at Umbraco.Core.Events.QueuingEventDispatcherBase.DispatchCancelable[TSender,TArgs](TypedEventHandler`2 eventHandler, TSender sender, TArgs args, String eventName)
at Umbraco.Core.Services.Implement.ContentService.StrategyCanPublish(IScope scope, IContent content, Boolean checkPath, IReadOnlyList`1 culturesPublishing, IReadOnlyCollection`1 culturesUnpublishing, EventMessages evtMsgs, ContentSavingEventArgs savingEventArgs)
at Umbraco.Core.Services.Implement.ContentService.CommitDocumentChangesInternal(IScope scope, IContent content, ContentSavingEventArgs saveEventArgs, Int32 userId, Boolean raiseEvents, Boolean branchOne, Boolean branchRoot)
at Umbraco.Core.Services.Implement.ContentService.SaveAndPublish(IContent content, String culture, Int32 userId, Boolean raiseEvents)
at Test.ContentComponent.Initialize() in C:\Users\benpa\Desktop\DELETE ME\Test\Test\ContentComponent.cs:line 27
at Umbraco.Core.Composing.ComponentCollection.Initialize()
at Umbraco.Core.Runtime.CoreRuntime.Boot(IRegister register, DisposableTimer timer)
Umbraco Version: 8.0.1
Homepage node created at root of content site.
Null reference exception thrown
What happens is this: the RedirectTrackingComponent uses the Current.UmbracoContext.HttpContext to store some state items that need to be carried over the different ContentService event handlers. In a IComponent when running Initialize, since there is no current request, there is no UmbracoContext, and it all ends in a NullReferenceException.
Turns out there is a way to ensure there is a context. Can you try with the following code?
~~~
public class ContentComponent : IComponent
{
private readonly IContentService _contentService;
private readonly IUmbracoContextFactory _contextFactory;
public ContentComponent(IContentService contentService, IUmbracoContextFactory contextFactory)
{
_contentService = contentService;
_contextFactory = contextFactory;
}
public void Initialize()
{
IContent homepageNode = _contentService.GetRootContent().FirstOrDefault(x => x.ContentType.Alias == "homepage");
if (homepageNode == null)
{
IContent homepage = _contentService.Create("Homepage", -1, "homepage");
using (_contextFactory.EnsureUmbracoContext)
{
_contentService.SaveAndPublish(homepage);
}
}
}
public void Terminate() { }
}
~~~
Two things to note:
HttpContext to hold some context... and we are slowly getting rid of them as they create an unwanted dependency to System.Web.Thanks @zpqrtbnk - worked an absolute treat! Thanks for the super speedy reply, really good information.
Actually, may have spoken too soon. I'm now getting the following error:
Value cannot be null.
Parameter name: siteUri
Stack trace:
at Umbraco.Web.Compose.NotificationsComponent.Notifier.SendNotification(IUser sender, IEnumerable`1 entities, IAction action, Uri siteUri)
at Umbraco.Web.Compose.NotificationsComponent.Notifier.Notify(IAction action, IContent[] entities)
at Umbraco.Web.Compose.NotificationsComponent.ContentServiceSaved(Notifier notifier, IContentService sender, SaveEventArgs`1 args, ActionCollection actions)
at Umbraco.Web.Compose.NotificationsComponent.<Initialize>b__3_3(IContentService sender, ContentSavedEventArgs args)
at Umbraco.Core.Events.TypedEventHandler`2.Invoke(TSender sender, TEventArgs e)
at Umbraco.Core.Events.EventDefinition`2.RaiseEvent()
at Umbraco.Core.Events.QueuingEventDispatcher.ScopeExitCompleted()
at Umbraco.Core.Events.QueuingEventDispatcherBase.ScopeExit(Boolean completed)
at Umbraco.Core.Scoping.Scope.<>c__DisplayClass72_0.<RobustExit>b__1()
at Umbraco.Core.Scoping.Scope.TryFinally(Int32 index, Action[] actions)
at Umbraco.Core.Scoping.Scope.TryFinally(Int32 index, Action[] actions)
at Umbraco.Core.Scoping.Scope.RobustExit(Boolean completed, Boolean onException)
at Umbraco.Core.Scoping.Scope.DisposeLastScope()
at Umbraco.Core.Scoping.Scope.Dispose()
at Umbraco.Core.Services.Implement.ContentService.SaveAndPublish(IContent content, String culture, Int32 userId, Boolean raiseEvents)
at Test.ContentComponent.Initialize() in C:\Users\benpa\Desktop\DELETE ME\Test\Test\ContentComponent.cs:line 43
at Umbraco.Core.Composing.ComponentCollection.Initialize()
at Umbraco.Core.Runtime.CoreRuntime.Boot(IRegister register, DisposableTimer timer)
Just to note, I think this worked because my homepage had half been created but when I deleted the content and tried again, I didn't get the original error but the one above.
It looks like the content node is created and published just fine.
Also, this part of your code:
using (_contextFactory.EnsureUmbracoContext)
Throws the following error:
'method group': type used in a using statement must be implicitly convertible to 'System.IDisposable' or implement a suitable 'Dispose' method.
I had originally changed this out for a method call like this:
using (_contextFactory.EnsureUmbracoContext())
Not 100% sure if that's correct or not.
Hope that all makes sense 馃槗
Oops, yes, it's a method call - thanks for correcting ;-)
Now about the other error - going to look into it.
Ok, so when you save the content item, we want to send a notification, and we want to know the Url of the site - but, that Url is determined when the first request hits the site. During a component Initialize method, it therefore is... null.
If we initialize it with an "empty" (not null) value, the notification message will look weird. We could try to run a first detection... but then you would have to configure it in config files, we could not rely on guessing the Url from the first request.
Of course another way would be to... wait for the first request (I think we have an event for this) and do what you have to do, only at that time - but that is not entirely satisfying either.
Mmmm... tough one, not exactly sure. Thoughts?
@zpqrtbnk Do you know what the "first request" event is? I'm experiencing the same problem. I'm trying to install some default content when my package is installed. I might be going about it completely the wrong way though! :)
I'm facing the same issue, I'm trying to import some data from a API using a BackgroundTaskRunner. The weird part is the save and publish does work at my end it just throws an error.
at LightInject.Web.PerWebRequestScopeManager.GetOrAddScope()
at LightInject.Web.PerWebRequestScopeManager.get_CurrentScope()
at LightInject.ServiceContainer.GetInstance(Type serviceType)
at Umbraco.Core.Composing.LightInject.LightInjectContainer.GetInstance(Type type)
at Umbraco.Core.FactoryExtensions.GetInstance[T](IFactory factory)
at Umbraco.Web.Runtime.WebInitialComposer.<>c.<Compose>b__0_6(IFactory factory)
at Umbraco.Core.Composing.LightInject.LightInjectContainer.<>c__DisplayClass20_01.<Register>b__0(IServiceFactory f)
at LightInject.ServiceContainer.GetInstance(Type serviceType)
at Umbraco.Core.Composing.LightInject.LightInjectContainer.GetInstance(Type type)
at Umbraco.Core.FactoryExtensions.GetInstance[T](IFactory factory)
at Umbraco.Deploy.Cloud.LiveEditing.EventSubscriber.GetHubAndHelper(IHubContext& hub, UmbracoHelper& helper)
at Umbraco.Deploy.Cloud.LiveEditing.EventSubscriber.ContentService_Published(IContentService sender, PublishEventArgs1 e)`
Ok, so when you save the content item, we want to send a notification, and we want to know the Url of the site - but, that Url is determined when the first request hits the site. During a component
Initializemethod, it therefore is...null.If we initialize it with an "empty" (not
null) value, the notification message will look weird. We could try to run a first detection... but then you would have to configure it in config files, we could not rely on guessing the Url from the first request.Of course another way would be to... wait for the first request (I think we have an event for this) and do what you have to do, only at that time - but that is not entirely satisfying either.
Mmmm... tough one, not exactly sure. Thoughts?
Been a while since I picked this up, I'm still seeing the issue and not entirely sure on the solution. I'm happy with the solution to run this on the first request (which I think it better than changing config files) if that's a thing but can't see anything about it documented or in the code.
can confirm this issue still occurs in Umbraco 8.2.0
and can also confirm @zpqrtbnk suggested workaround/fix still works as well :)
tx!
I hadn't seen this issue yet, it is essentially a duplicate of this https://github.com/umbraco/Umbraco-CMS/issues/6464
On startup, or anytime you need to use an UmbracoContext when there isn't one, you will need to use IUmbracoContextFactory.
The issue now though is that on startup you cannot save/publish things because notifications want to be sent and there is no application URL so you will get a null ref.
as part of https://github.com/umbraco/Umbraco-CMS/issues/6464 @KevinJump may make a 'quick fix' for this, but longer term we will need to sort out how notifications are sent: on a background task runner with a queue, also enusring that if any static app url is specified that we don't need to wait until first request, also that the app url isn't overwritten on every request, etc... I will close this for now, please follow #6464
Ok, so when you save the content item, we want to send a notification, and we want to know the Url of the site - but, that Url is determined when the first request hits the site. During a component
Initializemethod, it therefore is...null.
If we initialize it with an "empty" (notnull) value, the notification message will look weird. We could try to run a first detection... but then you would have to configure it in config files, we could not rely on guessing the Url from the first request.
Of course another way would be to... wait for the first request (I think we have an event for this) and do what you have to do, only at that time - but that is not entirely satisfying either.
Mmmm... tough one, not exactly sure. Thoughts?Been a while since I picked this up, I'm still seeing the issue and not entirely sure on the solution. I'm happy with the solution to run this on the first request (which I think it better than changing config files) if that's a thing but can't see anything about it documented or in the code.
Which event are you using? Can you show sample code where you subscribe to the event?
Most helpful comment
What happens is this: the
RedirectTrackingComponentuses theCurrent.UmbracoContext.HttpContextto store some state items that need to be carried over the differentContentServiceevent handlers. In aIComponentwhen runningInitialize, since there is no current request, there is noUmbracoContext, and it all ends in aNullReferenceException.Turns out there is a way to ensure there is a context. Can you try with the following code?
~~~
public class ContentComponent : IComponent
{
private readonly IContentService _contentService;
private readonly IUmbracoContextFactory _contextFactory;
}
~~~
Two things to note:
HttpContextto hold some context... and we are slowly getting rid of them as they create an unwanted dependency toSystem.Web.