How to Register DI Services in a Custom Module with Application Builder Extensions
- 4 minutes to read
This topic describes how to implement an extension class that you can use to implement logic required to register and configure services within a module project.
Note
The technique described in this topic only applies to .NET applications that use Application Builders.
You can use this technique to achieve one of the following:
- In your custom module, you can register and configure all services that the module requires in a centralized manner.
- In the application’s main module (the MySolution.Module project), you can register and configure all services that must be available across all platforms in the same configuration without the need to duplicate the registration code throughout the Startup.cs files in platform-specific projects (MySolution.Blazor.Server/Startup.cs, MySolution.Win/Startup.cs, and/or MySolution.WebApi/Startup.cs).
Add Module Extensions to a Module
To implement module extensions, follow the steps described below:
Add a static class (named
MySolutionModuleExtensions
in this example) to your module. Add a staticAddMySolutionModule
extension method to the class that extends theIModuleBuilder
interface. This method will be used to register your module in the Application Builder code within the Startup.cs files of application projects (see the Register the Module in Platform-Specific Applications section).File: MySolution.Module/MySolutionModuleExtensions.cs
public static class MySolutionModuleExtensions { public static IModuleBuilder<TBuilder> AddMySolutionModule<TBuilder>( this IModuleBuilder<TBuilder> builder) where TBuilder : IXafApplicationBuilder<TBuilder> { builder.Add<MySolutionModule>(); IServiceCollection services = builder.Context.Services; services.AddScoped<MyService>(); services.PostConfigure<MyServiceOptions>(options => { // ... }); // ... // The method must return the `builder` instance. return builder; } }
The
AddMySolutionModule
method receives thebuilder
parameter, which is an instance of the Application Builder. Usebuilder
to register the current module in the application and configure services.For better abstraction, you can implement additional extension methods (for example, for
IServiceCollection
) to split the configuration logic based on the task.The following code snippet demonstrates how to implement a module extensions class that incapsulates logic required to configure application security and non-persistent object spaces. These two tasks are implemented as separate extension methods (
ConfigureSecurity
andConfigureNonPersistentDataProvider
).File: MySolution.Module/MySolutionModuleExtensions.cs
public static class MySolutionModuleExtensions { public static IModuleBuilder<TBuilder> AddMySolutionModule<TBuilder>( this IModuleBuilder<TBuilder> builder) where TBuilder : IXafApplicationBuilder<TBuilder> { builder.Add<MySolutionModule>(); IServiceCollection services = builder.Context.Services; services.ConfigureSecurity(); services.ConfigureNonPersistentDataProvider(); // ... return builder; } static IServiceCollection ConfigureSecurity(this IServiceCollection services) { services.PostConfigure<SecurityOptions>(options => { options.Lockout.Enabled = true; options.Lockout.MaxFailedAccessAttempts = 3; options.RoleType = typeof(PermissionPolicyRole); options.UserType = typeof(ApplicationUser); options.UserLoginInfoType = typeof(ApplicationUserLoginInfo); options.SupportNavigationPermissionsForTypes = false; options.Events.OnSecurityStrategyCreated += securityStrategy => { ((SecurityStrategy)securityStrategy).PermissionsReloadMode = PermissionsReloadMode.CacheOnFirstAccess; }; }); return services; } static IServiceCollection ConfigureNonPersistentDataProvider(this IServiceCollection services) { services.AddSingleton<NonPersistentGlobalObjectStorage>(); services.PostConfigure<ObjectSpaceProviderOptions>(options => { options.Events.OnObjectSpaceCreated += context => { if(context.ObjectSpace is NonPersistentObjectSpace nonPersistentObjectSpace) { new NonPersistentObjectSpaceExtender(context.ServiceProvider, nonPersistentObjectSpace); } }; }); return services; } }
You can also find the full example in the MainDemo.Module\NonPersistentObjects\MainModuleExtensions.cs file in the Feature Center demo that is installed in the %PUBLIC%\Documents\DevExpress Demos 24.1\Components\XAF\FeatureCenter.NETFramework.XPO folder.
Pass Services to a Module Constructor
If you need to access a service from code in a module’s Module.cs
file (for example, in your module class’s Setup
method), adjust your application code as follows:
Add an argument of the required service’s type to your module’s constructor:
File: MySolution.MyModule/Module.cs
public sealed class MyModule : ModuleBase { MyService myService; public MyModule(MyService myService) { this.myService = myService; // ... } // ... }
Modify the module extensions code so that it passes the required service to the module constructor as shown below:
File: MySolution.Module/MySolutionModuleExtensions.cs
public static class MyModuleExtensions { public static IModuleBuilder<TBuilder> AddMyModule<TBuilder>( this IModuleBuilder<TBuilder> builder) where TBuilder : IXafApplicationBuilder<TBuilder> { builder.Add((serviceProvider) => { return new MyModule(serviceProvider.GetRequiredService<MyService>()); }); // ... return builder; } }
Register the Module in Platform-Specific Applications
To register your module and execute all configuration logic implemented in the module’s MySolutionModuleExtensions
class within a platform-specific application’s scope, add the following line to the Application Builder code in the application’s Startup.cs file:
File: MySolution.Blazor.Server/Startup.cs, MySolution.Win/Startup.cs, MySolution.WebApi/Startup.cs
// ...
builder.Modules
// ...
.AddMySolutionModule()
If the Startup.cs file already contains code that registers the module (builder.Modules.Add<MySolutionModule>
), be sure to remove this code.