core modules services - grecosoft/NetFusion GitHub Wiki
This topic will cover the concept of Module Services. The logic, implemented within modules, may need to be accessed by other modules within the same or different plugins.
For example, the NetFusion.Integration.RabbitMQ plugin defines the BusModule responsible for managing connections and the BusEntityModule responsible for creating and subscribing to queues and topics. The BusEntityModule is also responsible for recreating these entities when a lost connection is detected and is therefore dependent on the BusModule.
Not all plugin modules must expose their services if only containing initialization logic. A module's logic is exposed by completing the following:
- Define an interface representing the service being exposed
- Derive the interface from: IPluginModuleService
- Have the module providing the service implement the interface
- Define a property on the referencing module of the derived IPluginModuleService interface type
This section will expose a service for one of the modules defined within the Examples.Bootstrapping.CrossCut project to be accessed by other modules. This example will be very simple to focus on the concept. Start by defining a service interface to be implemented by the module in the following directory:
Examples.Bootstrapping.CrossCut/Plugin
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.CrossCut.Plugin;
public interface ICheckValidRange : IPluginModuleService
{
Tuple<int, int>? IsValidRange(int value);
}
Next, implement the interface within the CoreModuleOne module located in the directory: Examples.Bootstrapping.CrossCut/Plugin/Modules
using Microsoft.Extensions.Logging;
using NetFusion.Common.Base;
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.CrossCut.Plugin.Modules;
public class CoreModuleOne : PluginModule,
ICheckValidRange
{
private readonly List<Tuple<int, int>> _validRanges = new ();
public override void Initialize()
{
NfExtensions.Logger.Log<PluginModule>(
LogLevel.Information, $"Initializing: {GetType().Name}");
_validRanges.Add(Tuple.Create(5, 10));
_validRanges.Add(Tuple.Create(22, 31));
_validRanges.Add(Tuple.Create(42, 72));
_validRanges.Add(Tuple.Create(100, 105));
}
public override void Configure()
{
NfExtensions.Logger.Log<PluginModule>(
LogLevel.Information, $"Configuring: {GetType().Name}");
}
public Tuple<int, int>? IsValidRange(int value)
{
return _validRanges.FirstOrDefault(r => value >= r.Item1 && value <= r.Item2);
}
}
This section will reference the ICheckValidRange service from another plugin module. The service will be referenced by the AppModuleOne located within the Examples.Bootstrapping.App project. A module's service is referenced by defining a Public property with a setter of the service's interface type on the referencing plugin module as shown within the following file: Examples.Bootstrapping.App/Plugin/Modules/AppModuleOne
using System;
using Examples.Bootstrapping.App.Plugin.Configs;
using Examples.Bootstrapping.CrossCut.Plugin;
using Microsoft.Extensions.Logging;
using NetFusion.Common.Base;
using NetFusion.Core.Bootstrap.Plugins;
namespace Examples.Bootstrapping.App.Plugin.Modules;
public class AppModuleOne : PluginModule
{
private ICheckValidRange ValidRanges { get; set; } = null!; // <-- Service refrence
public override void Initialize()
{
NfExtensions.Logger.Log<PluginModule>(
LogLevel.Information, $"Initializing: {GetType().Name}");
NfExtensions.Logger.Log<PluginModule>(
LogLevel.Information, $"Host PluginId: {Context.AppHost.PluginId}");
var config = Context.Plugin.GetConfig<HelloWorldConfig>();
if (!string.IsNullOrEmpty(config.Message))
{
Console.WriteLine(
$"The host application with the name of: {Context.AppHost.Name} says Hello {config.Message}");
}
}
public override void Configure()
{
NfExtensions.Logger.Log<PluginModule>(
LogLevel.Information, $"Configuring: {GetType().Name}");
var range = ValidRanges.IsValidRange(102); // <-- Add the following lines to cal service
if (range != null)
{
NfExtensions.Logger.Log<AppModuleOne>(
LogLevel.Warning, $"102 is value range[{range.Item1}, {range.Item2}]");
}
}
}
Note how the referencing plugin module accesses the service within its Configure method. This assures that the service provided by the declaring plugin module has been initialized. Any information provided by a module's service should be populated within its Initialize method and consumed from the dependent plugin's Configuration methods.
Run the service and verify the log messages showing the two valid ranges:
cd ./src/Examples.Bootstrapping.WebApi/
dotnet run
When plugin modules are bootstrapped, NetFusion automatically sets all properties on a module referencing other module services. This is not accomplished by dependency-injection since the DI Container does not exist at this point. Before methods are called on a module, all public properties with public/private setters, declared as a type deriving from IPluginModuleService, are set to the corresponding module service.
While not common, a plugin module can implement more than one derived IPluginModuleService interface. This is usually not the case since plugin modules should be small and focused on a single task.
NetFusion also registers all plugin module services within the dependency-injection container as singleton services. This allows other components to inject a module service after the container has been created. These services can be injected into any component registered within the container. Since ASP.NET Core adds all WebApi controllers to the DI Container, the above ICheckValidRange service can be injected into a controller as follows:
Examples.Bootstrapping.WebApi/Controllers/RangeController.cs
using Examples.Bootstrapping.CrossCut.Plugin;
using Microsoft.AspNetCore.Mvc;
namespace Examples.Bootstrapping.WebApi.Controllers;
[ApiController, Route("api/ranges")]
public class RangeController : ControllerBase
{
private readonly ICheckValidRange _validRange;
public RangeController(ICheckValidRange validRange)
{
_validRange = validRange;
}
[HttpPost("check/{value}")]
public IActionResult Check([FromRoute]int value)
{
var range = _validRange.IsValidRange(value);
if (range == null)
{
return BadRequest($"{value} is not within a valid range");
}
return Ok(new
{
value,
minValue = range.Item1,
maxValue = range.Item2
});
}
}
Test the above controller by making the following requests:
curl -X POST http://localhost:5009/api/ranges/check/102
curl -X POST http://localhost:5009/api/ranges/check/202