ContentEvents in CMS 12

Vote:
 

Is there anything new with ContentEvents, registering of Optimizely types, or ServiceLocation in CMS 12 that I'm missing?

#275313
Mar 02, 2022 13:46
Vote:
 

Ok, so the line above works in a Controller.

I was trying to run this in an extension method for a plugin/addon/module in startup.cs, after .AddCms(). How can I hook up content events for an addon, now that I cannot use the InitializationModule anymore...?

#275314
Edited, Mar 02, 2022 14:03
Ted
Vote:
 

You can still use them, but for (startup) performance reasons we should limit the number of initialization modules.

But you could do something like the following instead:

services.Configure<IContentEvents>(x => x.PublishedContent += OnPublishedContent);

Edit: Ok, I could have sworn the above code worked before, I must be confused.

Here's a quick workaround without having to add an initialization module (for ConfigureServices in Startup):

var serviceProvider = services.BuildServiceProvider();

var contentEvents = serviceProvider.GetService<IContentEvents>();

contentEvents.PublishedContent += OnPublishedContent;
#275402
Edited, Mar 02, 2022 19:33
Vincent - Mar 04, 2022 8:32
Is that working for you, Ted? It doesn't seem working in my solution.
Ted - Mar 04, 2022 13:55
No it didn't, Vincent! Thanks for pointing it out! I added a workaround that I'll use myself until I figure out a better way.
Vincent - Mar 07, 2022 2:10
That makes sense :)
henriknystrom - Mar 07, 2022 2:46
The approach that's using the Configure extension method should work in theory, but without doing any deeper investigation I suspect that it has to do with how the default IContentEvents implementation is instantiated. It might work just fine when attaching handlers to other service events.
Vote:
 

As .NET events need to be attached to an instance and the ConfigureServices method is called before any instances are created, the best place to do this in an Web Application would probably be in the Configure method of your Startup class. To avoid the ServiceLocator, you can declare IContentEvents as an argument of the Configure method.

public void Configure(IApplicationBuilder app, IContentEvents contentEvents)
{
    contentEvents.PublishedContent += OnPublishedContent;

    // ...
}

If you don't have access to the Startup class, for example when building a reusable module, and you want to avoid creating an InitializableModule, you can create a class that implements Microsoft.Extensions.Hosting.IHostedService and attach your event handlers in the StartAsync method. Then let the Application register this service in the ConfigureServices method. This is essentially how the Initialize method of initialization modules are called. Hosted services are started just before the Configure method is called.

internal class ContentEventsSubscriber : IHostedService
{
    private readonly IContentEvents _contentEvents;

    public ContentEventsSubscriberIContentEvents contentEvents)
    {
        _contentEvents = contentEvents;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _contentEvents.PublishedContent += OnPublishedContent;
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _contentEvents.PublishedContent -= OnPublishedContent;
        return Task.CompletedTask;
    }

}
#275793
Mar 07, 2022 2:16
Ted - Mar 07, 2022 7:45
Thanks for the IHostedService tip, Henrik!
Tomas Hensrud Gulla - Mar 07, 2022 18:25
If I do not have access to the Startup class, how do I «let the Application register the service in the ConfigureServices method»? Isn't that typically inside the Startup class?
henriknystrom - Mar 07, 2022 20:11
Sorry, I probably should have explain that better. What I meant was that whoever owns the application would register the service in their Startup class. If you are a module developer, I would recommend that you would provide an IServiceCollection extension method that does this registration, similar to how AddMvc or AddCms registers services for their respective frameworks. I would say that this represents the convention for .NET 5 (and .NET Core before that).
If you want "automatic" registration as soon as the Nuget package is installed, which would be more inline with the previous CMS 11 and .NET Framework convention, you can continue to use an InitializationModule.
Tomas Hensrud Gulla - Mar 07, 2022 21:27
Got it! Thanks for explaining it, so even I can understand :-)
Vote:
 

It seems the ServiceLocator.Current at that time has not been created, and the value is null. You have two options here

  1. Use Ted's approach in your extension method to create an instance of serviceprovider instead of Optimizely ServiceLocator OR
  2. Continue with InitializationModule 

I personally will go #2 approach, as #1 used to be considered as code smell. Manually invoking this could lead unexpected behaviour (e.g. intensive singleton services are being re-created)

#275794
Mar 07, 2022 2:36
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.