London Dev Meetup Rescheduled! Due to unavoidable reasons, the event has been moved to 21st May. Speakers remain the same—any changes will be communicated. Seats are limited—register here to secure your spot!

Service registration in IConfigurableModule

L
L
Vote:
0

Hi,

I'm implemeting InitializationModule

[InitializableModule]
[ModuleDependency(typeof(InitializationModule))]
public class MyInitializationModule : IConfigurableModule
{
    public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Configure<ProtectedModuleOptions>(pm => pm.Items.Add(new ModuleDetails { Name = "MyModule" }));
        context.Services.AddSingleton<SomeType>();
        var serviceProvider = context.Services.BuildServiceProvider();

        var myType = serviceProvider.GetService<SomeType>(); //ERROR
    }
}

Error is `System.InvalidOperationException: No constructor for type 'EPiServer.Events.Internal.RemoteCacheSynchronization' can be instantiated using services from the service container and default values.`.

Ok, RemoteCacheSynchronization type needs some IObjectInstanceCache to instantiate, but it is module job to register it? Modules could use different caches, or what?

#275964
Mar 09, 2022 13:33
Vote:
0

In CMS 12 you should do that in the ConfigureServices method of your Startup class instead, avoiding initialization modules when possible.

#275965
Edited, Mar 09, 2022 13:39
L
Vote:
0

Since this is an independent module for customer's system I have planned to enclose everything in the InitializationModule, but you sugessting to register evething in Startup instead?

#275966
Mar 09, 2022 13:48
Ted - Mar 09, 2022 14:15
Yes, common pattern is to provide an extension method for IServiceCollection in your add-on/module, to allow the site to invoke AddYourAddonModule() in the site's Startup class, just like AddCms() or AddCommerce().
L
Vote:
0

Anyway from the Startup I also getting that error

#275972
Mar 09, 2022 14:15
Ted - Mar 09, 2022 14:19
You might be helped by this : https://world.optimizely.com/forum/developer-forum/cms-12/thread-container/2022/3/contentevents-in-cms-12/#275402

For an add-on, you could look into IHostedService (for things that would normally be done in the Configure method, such as getting an instance from the DI container).
L
Vote:
0

After adding

services.AddSingleton<IObjectInstanceCache, MemoryObjectInstanceCache>();

provider moves further but now it cannot find implememtation of EPiServer.Framework.ITimeProvider.
Why all of that is not registred in existed services.AddCms()?

No idea how to get a service I have just registred without ServiceProvider. Also ServiceLocator.Current.GetService(typeof(SomeType)) returns null.

#275975
Mar 09, 2022 14:29
Ted - Mar 09, 2022 14:51
Have a look at implementing IHostedService (info in that thread I linked earlier). If it's for the site (not the add-on) then you should get instances from the DI container by adding them to the Configure() method in Startup instead.
Vote:
0

Hi L,

I believe all the services you mention are registered by the FrameworkInitialization (EPiServer.Framework) initalizable module.

You should specify a dependency on it in your initializable module and see if that helps.

[InitializableModule]
[ModuleDependency(typeof(FrameworkInitialization))]
public class MyInitializationModule : IConfigurableModule {}
#276052
Mar 10, 2022 14:39
Vote:
0

Perhaps try to register yours in context.ConfigurationComplete event? 

context.ConfigurationComplete += (o, e) =>
            {
                e.Services.TryAddSingleton<blah blah blah ...
            };

#276059
Mar 10, 2022 16:51
Vote:
0

Quan's solution should be working, the ConfigurationComplete event should be the last thing executed during configuration. However, what I don't understand is why you register the service into container and then retrieve it immediately by forcing to create a new scope of the container?  Manually invoking BuildServiceProvider could lead unexpected behaviour (e.g. intensive singleton services are being re-created). If you need to reference to the instance of your class during the registration, I recommend going with manually instantiate the object 

public void ConfigureContainer(ServiceConfigurationContext context)
    {
        context.Services.Configure<ProtectedModuleOptions>(pm => pm.Items.Add(new ModuleDetails { Name = "MyModule" }));


        var someTypeInstance = new SomeType();
        context.Services.AddSingleton<SomeType>(someTypeInstance);
        

  
    }

#276788
Mar 21, 2022 4:18
Vote:
0

As you already stated in your original post - you can register types whenever you want. However, in order to create a instance of that type - all dependencies for that instance needs to exist in the registry as well. As the name ConfigureContainer suggests - the purpose of this method is to set up the service provider (for example StructureMap registry) - nothing else. You should not use the service provider to create instances, because the service provider is not yet ready.

So, you need to move serviceProvider.GetService<SomeType>() (which creates the instance) to something that occurs later - for example ConfigurationComplete, as Quan mentioned. At this point, the service provider is ready to use.

#276985
Mar 23, 2022 12:38
L
Vote:
0

Ok, now it seems to have sense. I was registering my services in the ConfigureContainer() method. Some of my classes had dependencies on Episerver stuff like EPiServer.DataAbstraction.ILanguageBranchRepository. Being still ConfigureContainer() method I needed to retrieve class that I registered and I was facing that problem with Epi part wasn't ready yet. Even after added 

[ModuleDependency(typeof(FrameworkInitialization))]

I moved retrieving my service to Initialize() method and now I can use it to initialize what I need :)

Also that suggestion to extract services registration to the separate extension method seems a better idea than keeping it in the module.

#277057
Mar 24, 2022 9:27
Vote:
0

Theory tells us that module has to be split into 2 parts: registration and usage.

That is the reason why we see services.AddSomething(); and app.UseSomething();. in our Startup.cs files.

So you should look for a way to split your module into registering your dependencies in phase #1 - configure container, and phase #2 - use those registered dependencies to carry out tasks during startup.

I was also struggling with this until I realize that I actually don't need that dependency yet - I can make use of it when everything is configured, the dependency container is built and the app is about to start.

#279353
Apr 27, 2022 20:45
* 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.