SlashPineTech.Forestry.ServiceModules
2.0.0
dotnet add package SlashPineTech.Forestry.ServiceModules --version 2.0.0
NuGet\Install-Package SlashPineTech.Forestry.ServiceModules -Version 2.0.0
<PackageReference Include="SlashPineTech.Forestry.ServiceModules" Version="2.0.0" />
paket add SlashPineTech.Forestry.ServiceModules --version 2.0.0
#r "nuget: SlashPineTech.Forestry.ServiceModules, 2.0.0"
// Install SlashPineTech.Forestry.ServiceModules as a Cake Addin #addin nuget:?package=SlashPineTech.Forestry.ServiceModules&version=2.0.0 // Install SlashPineTech.Forestry.ServiceModules as a Cake Tool #tool nuget:?package=SlashPineTech.Forestry.ServiceModules&version=2.0.0
Forestry .NET -- Service Modules
Forestry .NET is a set of open-source libraries for building modern web applications using ASP.NET Core.
This service modules package adds support for configuring services using an object-oriented approach.
Usage
Simple Example
Getting started with Service Modules is easy. Create a class that implements
IServiceModule
, like so:
public class BugsnagModule : IServiceModule
{
public void Configure(IServiceCollection services, IServiceConfigurationContext ctx)
{
services.AddBugsnag(configuration =>
{
configuration.ApiKey = "YourApiKeyGoesHere";
configuration.ReleaseStage = ctx.Environment.EnvironmentName;
});
}
}
Then, in your Startup
class, add the following:
services.AddModules(typeof(Startup).Assembly, Environment, Configuration)
.AddModule<BugsnagModule>();
That's the simplest use of service modules. However, we included our API key above in the code, which is a no-no. Let's look at how to bind our configuration to a module to keep the API key safe.
Configuration Binding
Continuing with our Bugsnag example above, let's consider a configuration section in our appsettings.json.
{
"Bugsnag": {
"ApiKey": "YourApiKeyGoesHere"
}
}
We can tell the service modules system to bind that configuration to our module
by adding the name of the config section to the AddModule<T>()
call.
services.AddModules(typeof(Startup).Assembly, Environment, Configuration)
.AddModule<BugsnagModule>("Bugsnag");
Let's revisit the example module and add a new property to bind to.
public class BugsnagModule : IServiceModule
{
public string? ApiKey { get; set; }
public void Configure(IServiceCollection services, IServiceConfigurationContext ctx)
{
if (!string.IsNullOrEmpty(ApiKey))
{
services.AddBugsnag(configuration =>
{
configuration.ApiKey = ApiKey;
configuration.ReleaseStage = ctx.Environment.EnvironmentName;
});
}
}
}
The value of our ApiKey
JSON property will be bound to the ApiKey
property in
our module. If we don't set an API key, we can check that the property is not null
and disable Bugsnag integration.
Configuration Validation
In the Bugsnag example, we didn't configure our services if the API key was null. What if we need a configuration property to be present. Let's consider an example that configures EF core.
{
"Database": {
"ConnectionString": "YourSqlServerConnectionStringGoesHere;",
"EnableSensitiveDataLogging": true
}
}
public class DatabaseModule : IServiceModule
{
[Required]
public string? ConnectionString { get; set; }
public bool EnableSensitiveDataLogging { get; set; } = false;
public void Configure(IServiceCollection services, IServiceConfigurationContext ctx)
{
services.AddDbContext<ApplicationDbContext>(options =>
{
options.UseSqlServer(ConnectionString!)
.EnableSensitiveDataLogging(EnableSensitiveDataLogging);
});
}
}
You can use any validation attributes on your properties in a module to ensure that your configuration values are provided. If validation fails, startup will halt, and error messages will be written to the log informing you of the validation errors.
services.AddModules(typeof(Startup).Assembly, Environment, Configuration)
.AddModule<BugsnagModule>("Bugsnag")
.AddModule<DatabaseModule>("Database");
Polymorphic Configuration Binding
So far, so good, right? But what if you want to bind to a different module at runtime based on our configuration? Let's consider an example for sending email via SMTP or an HTTP-based service like SendGrid.
Our configuration could like like either of these:
{
"Email": {
"Type": "Smtp",
"FromName": "My Web App",
"FromAddress": "no-reply@example.com",
"Hostname": "localhost",
"Port": 25
}
}
{
"Email": {
"Type": "SendGrid",
"FromName": "My Web App",
"FromAddress": "no-reply@example.com",
"ApiKey": "YourSendGridApiKey"
}
}
Next, let's look at our modules.
[ServiceModuleInfo(DefaultImpl = typeof(SmtpEmailModule))]
public abstract class EmailModule : IServiceModule
{
[Required]
public string? FromName { get; set; }
[Required]
[EmailAddress]
public string? FromAddress { get; set; }
public abstract void Configure(IServiceCollection services, IServiceConfigurationContext ctx);
}
[ServiceModuleName("Smtp")]
public class SmtpEmailModule : EmailModule
{
[Required]
public string? Host { get; set; }
public int Port { get; set; } = 25;
public override void Configure(IServiceCollection services, IServiceConfigurationContext ctx)
{
services.AddSingleton(_ => new SmtpSettings(
new EmailAddress(FromAddress!, FromName),
Host!,
Port
));
services.AddTransient<IEmailSender, SmtpEmailSender>();
}
}
[ServiceModuleName("SendGrid")]
public class SendGridEmailModule : EmailModule
{
[Required]
public string? ApiKey { get; set; }
public override void Configure(IServiceCollection services, IServiceConfigurationContext ctx)
{
services.AddSingleton(_ => new SendGridSettings(
new EmailAddress(FromAddress!, FromName),
ApiKey!
));
services.AddTransient<IEmailSender, SendGridEmailSender>();
}
}
Then we register our module in Startup
.
services.AddModules(typeof(Startup).Assembly, Environment, Configuration)
.AddModule<BugsnagModule>("Bugsnag")
.AddModule<DatabaseModule>("Database")
.AddModule<EmailModule>("Email");
When registering a polymorphic module, we peek at the Type
property to see
which implementation to bind to.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- JetBrains.Annotations (>= 2022.1.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.