mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Extract shared configuration helpers into Infrastructure.Configuration library (#248)
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
|
||||
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
|
||||
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Breweries\Features.Breweries.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.UserManagement\Features.UserManagement.csproj" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Infrastructure.Configuration;
|
||||
using Infrastructure.Jwt;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -32,9 +33,9 @@ public class JwtAuthenticationHandler(
|
||||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
// Use the same access-token secret source as TokenService to avoid mismatched validation.
|
||||
string? secret = configuration["ACCESS_TOKEN_SECRET"];
|
||||
string? secret = configuration[ConfigurationKeys.AccessTokenSecret];
|
||||
if (string.IsNullOrWhiteSpace(secret))
|
||||
secret = configuration["Jwt:SecretKey"];
|
||||
secret = configuration[ConfigurationKeys.JwtSecretKeyFallback];
|
||||
|
||||
if (string.IsNullOrWhiteSpace(secret))
|
||||
return AuthenticateResult.Fail("JWT secret is not configured");
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\API.Core\API.Core.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email\Infrastructure.Email.csproj" />
|
||||
<ProjectReference Include="..\..\Features\Features.Emails\Features.Emails.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Infrastructure.Configuration;
|
||||
using Infrastructure.Jwt;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -430,7 +431,7 @@ public class AuthSteps(ScenarioContext scenario)
|
||||
? user
|
||||
: throw new InvalidOperationException("registered username not found in scenario");
|
||||
|
||||
string secret = GetRequiredConfigValue("ACCESS_TOKEN_SECRET");
|
||||
string secret = GetRequiredConfigValue(ConfigurationKeys.AccessTokenSecret);
|
||||
scenario["accessToken"] = GenerateJwtToken(
|
||||
userId,
|
||||
username,
|
||||
@@ -449,7 +450,7 @@ public class AuthSteps(ScenarioContext scenario)
|
||||
? user
|
||||
: throw new InvalidOperationException("registered username not found in scenario");
|
||||
|
||||
string secret = GetRequiredConfigValue("CONFIRMATION_TOKEN_SECRET");
|
||||
string secret = GetRequiredConfigValue(ConfigurationKeys.ConfirmationTokenSecret);
|
||||
scenario["confirmationToken"] = GenerateJwtToken(
|
||||
userId,
|
||||
username,
|
||||
@@ -468,7 +469,7 @@ public class AuthSteps(ScenarioContext scenario)
|
||||
? user
|
||||
: throw new InvalidOperationException("registered username not found in scenario");
|
||||
|
||||
string secret = GetRequiredConfigValue("CONFIRMATION_TOKEN_SECRET");
|
||||
string secret = GetRequiredConfigValue(ConfigurationKeys.ConfirmationTokenSecret);
|
||||
scenario["confirmationToken"] = GenerateJwtToken(
|
||||
userId,
|
||||
username,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Database.Connection;
|
||||
|
||||
public static class ConfigurationHelpers
|
||||
{
|
||||
public static string GetKeyOrThrow(IConfiguration configuration, string key) =>
|
||||
configuration[key]
|
||||
?? throw new InvalidOperationException($"The {key} environment variable is not set.");
|
||||
}
|
||||
@@ -11,11 +11,14 @@
|
||||
<PackageReference Include="Microsoft.SqlServer.Types" Version="160.1000.6" />
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||
Version="9.0.0"
|
||||
Version="10.0.1"
|
||||
/>
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.DependencyInjection.Abstractions"
|
||||
Version="10.0.0"
|
||||
/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Data.Common;
|
||||
using Infrastructure.Configuration;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
@@ -19,7 +19,7 @@ public class DefaultSqlConnectionFactory(IConfiguration configuration) : ISqlCon
|
||||
{
|
||||
get
|
||||
{
|
||||
string? fullConnectionString = configuration["DB_CONNECTION_STRING"];
|
||||
string? fullConnectionString = configuration[ConfigurationKeys.DbConnectionString];
|
||||
if (!string.IsNullOrEmpty(fullConnectionString))
|
||||
return fullConnectionString;
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Infrastructure.Configuration;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
@@ -14,9 +15,15 @@ public static class SqlConnectionStringHelper
|
||||
) =>
|
||||
new()
|
||||
{
|
||||
DataSource = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_SERVER"),
|
||||
UserID = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_USER"),
|
||||
Password = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_PASSWORD"),
|
||||
DataSource = ConfigurationHelpers.GetKeyOrThrow(
|
||||
configuration,
|
||||
ConfigurationKeys.DbServer
|
||||
),
|
||||
UserID = ConfigurationHelpers.GetKeyOrThrow(configuration, ConfigurationKeys.DbUser),
|
||||
Password = ConfigurationHelpers.GetKeyOrThrow(
|
||||
configuration,
|
||||
ConfigurationKeys.DbPassword
|
||||
),
|
||||
TrustServerCertificate = ResolveTrustServerCertificate(configuration),
|
||||
Encrypt = true,
|
||||
};
|
||||
@@ -38,7 +45,10 @@ public static class SqlConnectionStringHelper
|
||||
try
|
||||
{
|
||||
SqlConnectionStringBuilder builder = MakeConfiguredConnectionBuilder(configuration);
|
||||
builder.InitialCatalog = ConfigurationHelpers.GetKeyOrThrow(configuration, "DB_NAME");
|
||||
builder.InitialCatalog = ConfigurationHelpers.GetKeyOrThrow(
|
||||
configuration,
|
||||
ConfigurationKeys.DbName
|
||||
);
|
||||
connectionString = builder.ConnectionString;
|
||||
return true;
|
||||
}
|
||||
@@ -95,7 +105,7 @@ public static class SqlConnectionStringHelper
|
||||
private static bool ResolveTrustServerCertificate(IConfiguration configuration)
|
||||
{
|
||||
return !bool.TryParse(
|
||||
configuration["DB_TRUST_SERVER_CERTIFICATE"],
|
||||
configuration[ConfigurationKeys.DbTrustServerCertificate],
|
||||
out bool trustServerCertificate
|
||||
) || trustServerCertificate;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="scripts/**/*.sql" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Database.Connection;
|
||||
using Infrastructure.Configuration;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
@@ -21,7 +22,7 @@ internal static class Program
|
||||
.Build();
|
||||
|
||||
bool clearDatabase = string.Equals(
|
||||
configuration["CLEAR_DATABASE"],
|
||||
configuration[ConfigurationKeys.ClearDatabase],
|
||||
"true",
|
||||
StringComparison.OrdinalIgnoreCase
|
||||
);
|
||||
|
||||
@@ -21,5 +21,6 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Features.Auth\Features.Auth.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,7 @@ using Features.Auth.Identity;
|
||||
using Features.Auth.Services;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using Infrastructure.Configuration;
|
||||
using Infrastructure.Jwt;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -27,9 +28,11 @@ public class TokenServiceRefreshTests
|
||||
.AddInMemoryCollection(
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
["ACCESS_TOKEN_SECRET"] = "test-access-secret-that-is-very-long-1234567890",
|
||||
["REFRESH_TOKEN_SECRET"] = "test-refresh-secret-that-is-very-long-1234567890",
|
||||
["CONFIRMATION_TOKEN_SECRET"] =
|
||||
[ConfigurationKeys.AccessTokenSecret] =
|
||||
"test-access-secret-that-is-very-long-1234567890",
|
||||
[ConfigurationKeys.RefreshTokenSecret] =
|
||||
"test-refresh-secret-that-is-very-long-1234567890",
|
||||
[ConfigurationKeys.ConfirmationTokenSecret] =
|
||||
"test-confirmation-secret-that-is-very-long-1234567890",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ using Domain.Exceptions;
|
||||
using Features.Auth.Services;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using Infrastructure.Configuration;
|
||||
using Infrastructure.Jwt;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Moq;
|
||||
@@ -23,9 +24,11 @@ public class TokenServiceValidationTests
|
||||
.AddInMemoryCollection(
|
||||
new Dictionary<string, string?>
|
||||
{
|
||||
["ACCESS_TOKEN_SECRET"] = "test-access-secret-that-is-very-long-1234567890",
|
||||
["REFRESH_TOKEN_SECRET"] = "test-refresh-secret-that-is-very-long-1234567890",
|
||||
["CONFIRMATION_TOKEN_SECRET"] =
|
||||
[ConfigurationKeys.AccessTokenSecret] =
|
||||
"test-access-secret-that-is-very-long-1234567890",
|
||||
[ConfigurationKeys.RefreshTokenSecret] =
|
||||
"test-refresh-secret-that-is-very-long-1234567890",
|
||||
[ConfigurationKeys.ConfirmationTokenSecret] =
|
||||
"test-confirmation-secret-that-is-very-long-1234567890",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
|
||||
<ProjectReference Include="..\..\Domain\Domain.Exceptions\Domain.Exceptions.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.PasswordHashing\Infrastructure.PasswordHashing.csproj" />
|
||||
<ProjectReference Include="..\..\Database\Database.Connection\Database.Connection.csproj" />
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using Domain.Exceptions;
|
||||
using Features.Auth.Identity;
|
||||
using Infrastructure.Configuration;
|
||||
using Infrastructure.Jwt;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -33,23 +34,20 @@ public class TokenService : ITokenService
|
||||
_tokenInfrastructure = tokenInfrastructure;
|
||||
_userManager = userManager;
|
||||
|
||||
_accessTokenSecret =
|
||||
configuration["ACCESS_TOKEN_SECRET"]
|
||||
?? throw new InvalidOperationException(
|
||||
"ACCESS_TOKEN_SECRET environment variable is not set"
|
||||
);
|
||||
_accessTokenSecret = ConfigurationHelpers.GetKeyOrThrow(
|
||||
configuration,
|
||||
ConfigurationKeys.AccessTokenSecret
|
||||
);
|
||||
|
||||
_refreshTokenSecret =
|
||||
configuration["REFRESH_TOKEN_SECRET"]
|
||||
?? throw new InvalidOperationException(
|
||||
"REFRESH_TOKEN_SECRET environment variable is not set"
|
||||
);
|
||||
_refreshTokenSecret = ConfigurationHelpers.GetKeyOrThrow(
|
||||
configuration,
|
||||
ConfigurationKeys.RefreshTokenSecret
|
||||
);
|
||||
|
||||
_confirmationTokenSecret =
|
||||
configuration["CONFIRMATION_TOKEN_SECRET"]
|
||||
?? throw new InvalidOperationException(
|
||||
"CONFIRMATION_TOKEN_SECRET environment variable is not set"
|
||||
);
|
||||
_confirmationTokenSecret = ConfigurationHelpers.GetKeyOrThrow(
|
||||
configuration,
|
||||
ConfigurationKeys.ConfirmationTokenSecret
|
||||
);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email\Infrastructure.Email.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email.Templates\Infrastructure.Email.Templates.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Infrastructure.Configuration;
|
||||
using Infrastructure.Email;
|
||||
using Infrastructure.Email.Templates.Rendering;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
@@ -21,8 +22,7 @@ public class EmailDispatcher(
|
||||
/// Thrown when the <c>WEBSITE_BASE_URL</c> configuration value is not set.
|
||||
/// </exception>
|
||||
private string WebsiteBaseUrl =>
|
||||
configuration["WEBSITE_BASE_URL"]
|
||||
?? throw new InvalidOperationException("WEBSITE_BASE_URL environment variable is not set");
|
||||
ConfigurationHelpers.GetKeyOrThrow(configuration, ConfigurationKeys.WebsiteBaseUrl);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SendRegistrationEmailAsync(
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
namespace Infrastructure.Configuration;
|
||||
|
||||
public static class ConfigurationHelpers
|
||||
{
|
||||
public static string GetKeyOrThrow(IConfiguration configuration, string key) =>
|
||||
configuration[key]
|
||||
?? throw new InvalidOperationException($"The {key} environment variable is not set.");
|
||||
|
||||
public static bool GetBooleanKeyOrThrow(IConfiguration configuration, string key)
|
||||
{
|
||||
string value = GetKeyOrThrow(configuration, key);
|
||||
|
||||
return bool.TryParse(value, out bool result)
|
||||
? result
|
||||
: throw new InvalidOperationException(
|
||||
$"The {key} environment variable is set to '{value}', which is not a valid boolean."
|
||||
);
|
||||
}
|
||||
|
||||
public static int GetIntKeyOrThrow(IConfiguration configuration, string key)
|
||||
{
|
||||
string value = GetKeyOrThrow(configuration, key);
|
||||
|
||||
return int.TryParse(value, out int result)
|
||||
? result
|
||||
: throw new InvalidOperationException(
|
||||
$"The {key} environment variable is set to '{value}', which is not a valid integer."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
namespace Infrastructure.Configuration;
|
||||
|
||||
public static class ConfigurationKeys
|
||||
{
|
||||
public const string AccessTokenSecret = "ACCESS_TOKEN_SECRET";
|
||||
public const string RefreshTokenSecret = "REFRESH_TOKEN_SECRET";
|
||||
public const string ConfirmationTokenSecret = "CONFIRMATION_TOKEN_SECRET";
|
||||
|
||||
/// <summary>
|
||||
/// Fallback configuration section path for the access-token signing key, checked when
|
||||
/// <see cref="AccessTokenSecret" /> is not set.
|
||||
/// </summary>
|
||||
public const string JwtSecretKeyFallback = "Jwt:SecretKey";
|
||||
|
||||
public const string WebsiteBaseUrl = "WEBSITE_BASE_URL";
|
||||
|
||||
public const string SmtpHost = "SMTP_HOST";
|
||||
public const string SmtpPort = "SMTP_PORT";
|
||||
public const string SmtpUsername = "SMTP_USERNAME";
|
||||
public const string SmtpPassword = "SMTP_PASSWORD";
|
||||
public const string SmtpUseSsl = "SMTP_USE_SSL";
|
||||
public const string SmtpFromEmail = "SMTP_FROM_EMAIL";
|
||||
public const string SmtpFromName = "SMTP_FROM_NAME";
|
||||
|
||||
public const string DbServer = "DB_SERVER";
|
||||
public const string DbUser = "DB_USER";
|
||||
public const string DbPassword = "DB_PASSWORD";
|
||||
public const string DbName = "DB_NAME";
|
||||
public const string DbConnectionString = "DB_CONNECTION_STRING";
|
||||
public const string DbTrustServerCertificate = "DB_TRUST_SERVER_CERTIFICATE";
|
||||
|
||||
public const string ClearDatabase = "CLEAR_DATABASE";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>Infrastructure.Configuration</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference
|
||||
Include="Microsoft.Extensions.Configuration.Abstractions"
|
||||
Version="10.0.1"
|
||||
/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -13,4 +13,8 @@
|
||||
Version="10.0.1"
|
||||
/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Infrastructure.Configuration\Infrastructure.Configuration.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using MailKit.Net.Smtp;
|
||||
using Infrastructure.Configuration;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using MimeKit;
|
||||
@@ -25,31 +26,27 @@ public class SmtpEmailProvider : IEmailProvider
|
||||
/// <c>SMTP_FROM_EMAIL</c>, <c>SMTP_FROM_NAME</c>) from <paramref name="configuration" />.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when <c>SMTP_HOST</c> or <c>SMTP_FROM_EMAIL</c> is not set, or when <c>SMTP_PORT</c> is not a valid integer.
|
||||
/// Thrown when <c>SMTP_HOST</c>, <c>SMTP_PORT</c>, or <c>SMTP_FROM_EMAIL</c> is not set,
|
||||
/// or when <c>SMTP_PORT</c> is not a valid integer.
|
||||
/// </exception>
|
||||
public SmtpEmailProvider(IConfiguration configuration)
|
||||
{
|
||||
_host =
|
||||
configuration["SMTP_HOST"]
|
||||
?? throw new InvalidOperationException("SMTP_HOST environment variable is not set");
|
||||
_host = ConfigurationHelpers.GetKeyOrThrow(configuration, ConfigurationKeys.SmtpHost);
|
||||
|
||||
string portString = configuration["SMTP_PORT"] ?? "587";
|
||||
if (!int.TryParse(portString, out _port))
|
||||
throw new InvalidOperationException($"SMTP_PORT '{portString}' is not a valid integer");
|
||||
_port = ConfigurationHelpers.GetIntKeyOrThrow(configuration, ConfigurationKeys.SmtpPort);
|
||||
|
||||
_username = configuration["SMTP_USERNAME"];
|
||||
_password = configuration["SMTP_PASSWORD"];
|
||||
_username = configuration[ConfigurationKeys.SmtpUsername];
|
||||
_password = configuration[ConfigurationKeys.SmtpPassword];
|
||||
|
||||
string useSslString = configuration["SMTP_USE_SSL"] ?? "true";
|
||||
string useSslString = configuration[ConfigurationKeys.SmtpUseSsl] ?? "true";
|
||||
_useSsl = bool.Parse(useSslString);
|
||||
|
||||
_fromEmail =
|
||||
configuration["SMTP_FROM_EMAIL"]
|
||||
?? throw new InvalidOperationException(
|
||||
"SMTP_FROM_EMAIL environment variable is not set"
|
||||
);
|
||||
_fromEmail = ConfigurationHelpers.GetKeyOrThrow(
|
||||
configuration,
|
||||
ConfigurationKeys.SmtpFromEmail
|
||||
);
|
||||
|
||||
_fromName = configuration["SMTP_FROM_NAME"] ?? "The Biergarten";
|
||||
_fromName = configuration[ConfigurationKeys.SmtpFromName] ?? "The Biergarten";
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
Reference in New Issue
Block a user