November Happy Hour will be moved to Thursday December 5th.

Service registration in IConfigurableModule

L
L
Vote:
 

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
Ted
Vote:
 

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:
 

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:
 

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:
 

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:
 

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:
 

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:
 

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:
 

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:
 

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:
 

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.