Skip to content

Modules

Frank Wagner edited this page May 11, 2021 · 31 revisions

Getting started

With Hosuto modules you can create isolated parts of your application that will all run in their own host but share global settings and a DI container. As each module has it's own service provider you can register different services in each module. For example if you host a server component in one module you could configure another module to host a client of the same component. If you now distribute the application you can choose to run each module in it's own application or to run the modules in the same application.

Usage

To use modules first create a modules host in your applications main method. This host replaces the default .Net Generic Host.

Add a reference to the nuget package Dbosoft.Hosuto.Hosting and build the modules host from a ModulesHostBuilder:

        static Task Main(string[] args)
        {

            var builder = ModulesHost.CreateDefaultBuilder(args);
            
            // you can configure a modules host builder like a host builder.
            // All configurations set with ConfigureHostConfiguration will be shared between all modules.
            builder.UseEnvironment(EnvironmentName.Development);
            return builder.RunConsoleAsync();

        }

Then you can define your modules. A module is typical placed in its own assembly but this is not required. Modules have a convention based setup logic like the Startup class for Asp.Net Core.

    public class SimpleModule
    {
        public void ConfigureServices(
            IServiceCollection services)
        {

            [...]

The configure service method will configure the DI container of the module host. To do something useful you will typical have to register at least one HostedServices here.

The module has now to be added to the modules host builder:

        static Task Main(string[] args)
        {

            var builder = ModulesHost.CreateDefaultBuilder(args);
            
            [...]
            
            builder.HostModule<SomeModule>();
            return builder.RunConsoleAsync();

        }

Asp.Net Core

Hosuto supports also hosting of Asp.Net Core WebApplications in modules.
To enable the aspnetcore support you first have to add the package Dbosoft.Hosuto.Hosting.AspNetCore.

Then call method UseAspNetCore on ModulesHostBuilder:

For Asp.NetCore 3.0 or higher:

            builder.HostModule<SampleWebModule>();
            builder.UseAspNetCoreWithDefaults((module, webBuilder) =>
            {
            });

or without defaults

            builder.HostModule<SampleWebModule>();
            builder.UseAspNetCore((module, webBuilder) =>
            {
            });

For Asp.Net Core 2.0 and 2.1:

            builder.HostModule<SampleWebModule>();
            builder.UseAspNetCore(() => WebHost.CreateDefaultBuilder(args), (module, webBuilder) =>
            {
            });

Web modules

For AspNetCore the Module has to be declared with interface IWebModule or as WebModule. As the module conventions are compatible with the AspNet.Core startup class conventions you use the Startup class as module and inherits it from WebModule or IWebModule:

    public class SampleWebModule : WebModule
    {
        // this is optional, see sample of UseAspNetCore with HttpSys below for usage
        public override string Path => "/sample";

        public SampleWebModule(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {


            [...]

Please note that if you host multiple web modules in one application each will use their own Http server.
Therefore you have to configure each module to its own port (Kestrel) / path (HttpSys).

For Kestrel you should assign a unique port to each module.
On Windows you can also use http.sys. This has the advantage that http.sys supports port sharing and therefore you could use the path of the module instead of a port:

            .UseAspNetCore((module, webHostBuilder) =>
            {
               webHostBuilder.UseHttpSys(options =>
               {
                   options.UrlPrefixes.Add($"https://localhost:8080{module.Path}");
               })
               .UseUrls($"https://localhost:8080{module.Path}");
            })

Content and Razor/Blazor support

For a ApiApp based Web Module you can use any project template.
If you would like to use Blazor, Razor or assets in you WebModule you should create it first as normal Web Application from the templates.

Afterwards change project SDK from Microsoft.NET.Sdk.Web to Microsoft.NET.Sdk.Razor and add also the StaticWebAssetBasePath property as in this sample:

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
    <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
    <StaticWebAssetBasePath>.modules/$(AssemblyName)</StaticWebAssetBasePath>

  </PropertyGroup>

This will enable automatic handling of assets for the module, so that assets are available in Development and after packageing.

Content for AspNetCore 2.x

Deprectated for AspNetCore >= 3.x!!

You can use the interface INamedModule to assign a name for the module.
This has been added for compatibilty with ASPNetCore 2.x where it will be used to set the content root.

In that case you have to use the same name for module and the project folder of the module.
For example the SampleWebModule from the code above has both a project folder name 'SampleWebModule' and Name="SampleWebModule" (see also https://github.com/dbosoft/Hosuto/tree/master/samples/dotnetcore21).

Also project files of modules and all applications using these modules have to be in the same project directory.

After publishing also the application files have to be placed by following this convention:

  • MySuperApp
    • MySuperAppRoot <-- content root, typical binary folder
    • ModuleA <-- content of module A
    • ModuleB <-- content of module B

Module Configuration

You can configure modules from the builder as in a normal single host application.

All calls of ConfigureServices and ConfigureAppConfiguration will be applied to all modules hosted in the ModulesHost.
This includes also the extension methods for logging and so on.

     // ...    

    .ConfigureAppConfiguration(config => config
          // ...
    ).ConfigureLogging(l=>
    {
        l.SetMinimumLevel(LogLevel.Trace);
    });

Individual module configuration from builder is also possible:

    // ...  
    .HostModule<MyModule>(options =>
    {
        options.Configure(hostBuilder =>
            {
                // ...
            }
        );
    })

Dependency Injection and DI Containers in Modules

The .NET generic host and AspNetCore host have a build in DI container (available as IServiceProvider). It will be build by the ConfigureServices method and can be used in Configure method (AspNetCore) or for hosted services.

However, when it comes to complex applications having just one DI container may cause incompatibilties between different setups. For example enabling EfCore with more than one context requires a different setup as with one context.

Therefore - in Hosuto - each module will have it's own DI Container. It will by build up by Hosuto automatically from a internal HostBuilder with all the configuration you have applied to ModulesHost.

To inject services from the application (where you are building the ModulesHosts) you can optionally set up a additional DI container.
This container will be available for all modules.

Service Collection container

Hosuto has build-in support for using ServiceCollection as shared DI container. You can enable it with the UseServiceCollection method:

    var services = new ServiceCollection()
        .Add...

    // create ModulesHosts with services, service provider will be 
    // created automatically
    return ModulesHost.CreateDefaultBuilder(args)
        .UseServiceCollection(services)

The service provider can be used within modules by following convention:
If the first argument of ConfigureServices or Configure is a IServiceProvider than it will be the shared container!

    public class SimpleModule
    {
        public void ConfigureServices(
            IServiceProvider sharedServiceProvder,
            IServiceCollection services)
        {

            [...]

In method Configure both containers are available:

  public void Configure(IServiceProvider sharedServiceProvider, IApplicationBuilder app)
  {
    // module serviceProvider
    app.ApplicationServices
  }

SimpleInjector

Hosuto has a optional package to enable support for SimpleInjector: Dbosoft.Hosuto.SimpleInjector

Shared SimpleInjector Container

With this package you can setup the shared container like this:

    var container = new Container();

    ModulesHost.CreateDefaultBuilder(args)
        .UseSimpleInjector(container)

You can now access the container as IServiceProvider with the same convention as explained above.

SimpleInjector Container for Module

The default behaviour of UseSimpleInjector is to create also a additional Container for each module.
This container will be automatically integrated with the ServiceCollection based container.

When using the module container following additional (optional) methods will be called by convention:

  // configures the module SimpleInjector container. The IServiceProvider argument is optional
  public virtual void ConfigureContainer(IServiceProvider sharedServiceProvider, Container container)
  {

  }

  // configures how the SimpleInjector container is added to the Service Collection container
  public void AddSimpleInjector(SimpleInjectorAddOptions options)
  {
     // example of usage for AspNetCore
     options.AddAspNetCore()
            .AddControllerActivation();
  }

See also: AddSimpleInjector usage example for cross-wiring AspNetCore

The module container can be disabled by called UseSimpleInjector with false on second argument: UseSimpleinjector(container,false)