Skip to content

Commit

Permalink
Merge pull request #277 from Lombiq/issue/OFFI-92
Browse files Browse the repository at this point in the history
OFFI-92: Adding defaults for hosting-related configuration
  • Loading branch information
wAsnk authored Aug 6, 2024
2 parents bce6104 + fda03ab commit 2111aa6
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 2 deletions.
1 change: 1 addition & 0 deletions Lombiq.HelpfulLibraries.AspNetCore/Docs/Extensions.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Lombiq Helpful Libraries - ASP.NET Core Libraries - Extensions

- `ConfigurationSectionExtensions`: Provides shortcuts for `IConfigurationSection` operations.
- `CookieHttpContextExtensions`: Provides shortcuts for some cookie-related operations.
- `DateTimeHttpContextExtensions`: Makes it possible to set or get IANA time-zone IDs in the HTTP context.
- `EnvironmentHttpContextExtensions`: Provides shortcuts to determine information about the current hosting environment, like whether the app is running in Development mode.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Microsoft.Extensions.Configuration;

/// <summary>
/// Shortcuts for <see cref="IConfigurationSection"/> operations.
/// </summary>
public static class ConfigurationSectionExtensions
{
/// <summary>
/// Adds a value to a configuration section if the key doesn't exist yet.
/// </summary>
/// <param name="key">The key of the configuration.</param>
/// <param name="value">The value of the configuration.</param>
public static IConfigurationSection AddValueIfKeyNotExists(
this IConfigurationSection configurationSection,
string key,
string value)
{
configurationSection[key] ??= value;
return configurationSection;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
namespace Microsoft.Extensions.Configuration;

public static class ConfigurationExtensions
public static class AzureConfigurationExtensions
{
public const string IsAzureHostingKey = "IsAzureHosting";

/// <summary>
/// Retrieves a value indicating whether the <c>OrchardCore:IsAzureHosting</c> configuration key is set to <see
/// langword="true"/>.
/// </summary>
public static bool IsAzureHosting(
this IConfiguration configuration) =>
configuration.GetValue<bool>("OrchardCore:IsAzureHosting");
configuration.GetValue<bool>("OrchardCore:" + IsAzureHostingKey);
}
1 change: 1 addition & 0 deletions Lombiq.HelpfulLibraries.OrchardCore/Docs/Environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
## Extensions

- `FeatureInfoEnumerableExtensions`: Shortcuts for `IEnumerable<IFeatureInfo>`, like `Any(featureId)`.
- `HostingDefaultsOrchardCoreBuilderExtensions`: Lombiq-recommended opinionated default configuration for features of a standard Orchard Core application, including one hosted in Azure. It substitutes much of what you'd write as configuration in a `Program` class or _appsettings.json_ files.
- `OrchardCoreBuilderExtensions`: Shortcuts that can be used when initializing Orchard with `OrchardCoreBuilder`, e.g. `AddOrchardCms()`.
- `ShellFeaturesManagerExtensions`: Shortcuts for `IShellFeaturesManager`, like `IsFeatureEnabledAsync()`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

namespace Microsoft.Extensions.DependencyInjection;

public static class HostingDefaultsOrchardCoreBuilderExtensions
{
/// <summary>
/// Lombiq-recommended opinionated default configuration for features of a standard Orchard Core application. If
/// any of the configuration values exist, they won't be overridden, so e.g. appsettings.json configuration will
/// take precedence.
/// </summary>
/// <param name="webApplicationBuilder">The <see cref="WebApplicationBuilder"/> instance of the app.</param>
/// <param name="hostingConfiguration">Configuration for the hosting defaults.</param>
public static OrchardCoreBuilder ConfigureHostingDefaults(
this OrchardCoreBuilder builder,
WebApplicationBuilder webApplicationBuilder,
HostingConfiguration hostingConfiguration = null)
{
hostingConfiguration ??= new HostingConfiguration();

// Not using static type references for the names here because those practically never change, but we'd need to
// add project/package references to all the affected projects.

var ocSection = webApplicationBuilder.Configuration.GetSection("OrchardCore");

ocSection.GetSection("OrchardCore_Tenants").AddValueIfKeyNotExists("TenantRemovalAllowed", "true");

ocSection.GetSection("OrchardCore_Localization_CultureOptions").AddValueIfKeyNotExists("IgnoreSystemSettings", "true");

var shellsDatabaseSection = ocSection.GetSection("OrchardCore_Shells_Database");

shellsDatabaseSection.AddValueIfKeyNotExists("DatabaseProvider", "SqlConnection");
shellsDatabaseSection.AddValueIfKeyNotExists("TablePrefix", "Shells");

ocSection.GetSection("OrchardCore_Tenants").AddValueIfKeyNotExists("TenantRemovalAllowed", "true");

var logLevelSection = webApplicationBuilder.Configuration.GetSection("Logging:LogLevel");
var elasticSearchSection = ocSection.GetSection("OrchardCore_Elasticsearch");

if (webApplicationBuilder.Environment.IsDevelopment())
{
logLevelSection
.AddValueIfKeyNotExists("Default", "Debug")
.AddValueIfKeyNotExists("System", "Information")
.AddValueIfKeyNotExists("Microsoft", "Information");

// Orchard Core 1.8 and prior, this can be removed after an Orchard Core upgrade to 2.0.
// OrchardCore_Email_Smtp below is 2.0+.
var oc18SmtpSection = ocSection.GetSection("SmtpSettings");

if (oc18SmtpSection["Host"] == null)
{
oc18SmtpSection["Host"] = "127.0.0.1";
oc18SmtpSection["RequireCredentials"] = "false";
oc18SmtpSection["Port"] = "25";
}

oc18SmtpSection.AddValueIfKeyNotExists("DefaultSender", "[email protected]");

var smtpSection = ocSection.GetSection("OrchardCore_Email_Smtp");

if (smtpSection["Host"] == null)
{
smtpSection["Host"] = "127.0.0.1";
smtpSection["RequireCredentials"] = "false";
smtpSection["Port"] = "25";
}

smtpSection.AddValueIfKeyNotExists("DefaultSender", "[email protected]");

if (elasticSearchSection["Url"] == null)
{
elasticSearchSection["ConnectionType"] = "SingleNodeConnectionPool";
elasticSearchSection["Url"] = "http://localhost";
elasticSearchSection["Ports:0"] = "9200";
elasticSearchSection["Username"] = "admin";
elasticSearchSection["Password"] = "admin";
}
}
else
{
logLevelSection
.AddValueIfKeyNotExists("Default", "Warning")
.AddValueIfKeyNotExists("Microsoft.AspNetCore", "Warning");

ocSection.AddValueIfKeyNotExists("DatabaseProvider", "SqlConnection");

// Elastic Cloud configuration if none is provided. The Url and Password are still needed.
if (elasticSearchSection["ConnectionType"] == null &&
elasticSearchSection["Ports"] == null &&
elasticSearchSection["Username"] == null)
{
elasticSearchSection["ConnectionType"] = "CloudConnectionPool";
elasticSearchSection["Ports:0"] = "9243";
elasticSearchSection["Username"] = "elastic";
}
}

if (hostingConfiguration.AlwaysEnableHealthChecksInProduction && webApplicationBuilder.Environment.IsProduction())
{
builder.AddTenantFeatures("OrchardCore.HealthChecks");
}

builder
.AddDatabaseShellsConfigurationIfAvailable(webApplicationBuilder.Configuration)
.ConfigureSmtpSettings(overrideAdminSettings: false)
.ConfigureSecurityDefaultsWithStaticFiles(allowInlineStyle: true);

return builder;
}

/// <summary>
/// Lombiq-recommended opinionated default configuration for features of an Orchard Core application hosted in
/// Azure. If any of the configuration values exist, they won't be overridden, so e.g. appsettings.json
/// configuration will take precedence.
/// </summary>
/// <param name="webApplicationBuilder">The <see cref="WebApplicationBuilder"/> instance of the app.</param>
/// <param name="hostingConfiguration">Configuration for the hosting defaults.</param>
public static OrchardCoreBuilder ConfigureAzureHostingDefaults(
this OrchardCoreBuilder builder,
WebApplicationBuilder webApplicationBuilder,
AzureHostingConfiguration hostingConfiguration = null)
{
hostingConfiguration ??= new AzureHostingConfiguration();

builder.ConfigureHostingDefaults(webApplicationBuilder, hostingConfiguration);

var ocSection = webApplicationBuilder.Configuration.GetSection("OrchardCore");

if (!webApplicationBuilder.Environment.IsDevelopment())
{
ocSection.AddValueIfKeyNotExists(AzureConfigurationExtensions.IsAzureHostingKey, "true");
}

if (webApplicationBuilder.Configuration.IsAzureHosting())
{
builder
.AddTenantFeatures(
"OrchardCore.DataProtection.Azure",
"Lombiq.Hosting.BuildVersionDisplay")
.DisableResourceDebugMode();

if (hostingConfiguration.AlwaysEnableAzureMediaStorage)
{
// Azure Media Storage and its dependencies. Keep this updated with Orchard upgrades.
builder.AddTenantFeatures(
"OrchardCore.Contents",
"OrchardCore.ContentTypes",
"OrchardCore.Liquid",
"OrchardCore.Media",
"OrchardCore.Media.Azure.Storage",
"OrchardCore.Media.Cache",
"OrchardCore.Settings");
}
}

var mediaSection = ocSection.GetSection("OrchardCore_Media_Azure");

mediaSection.AddValueIfKeyNotExists("ContainerName", "media");
mediaSection.AddValueIfKeyNotExists("BasePath", "{{ ShellSettings.Name }}");

if (webApplicationBuilder.Environment.IsDevelopment())
{
var dataProtectionSection = ocSection.GetSection("OrchardCore_DataProtection_Azure");

dataProtectionSection.AddValueIfKeyNotExists("CreateContainer", "true");
dataProtectionSection.AddValueIfKeyNotExists("ConnectionString", "UseDevelopmentStorage=true");

mediaSection.AddValueIfKeyNotExists("CreateContainer", "true");
mediaSection.AddValueIfKeyNotExists("ConnectionString", "UseDevelopmentStorage=true");
}

return builder;
}
}

public class HostingConfiguration
{
/// <summary>
/// Gets or sets a value indicating whether to always enable <c>OrchardCore.HealthChecks</c> and its dependencies in
/// the Production environment, for all tenants, without the ability to turn them off.
/// </summary>
public bool AlwaysEnableHealthChecksInProduction { get; set; } = true;
}

public class AzureHostingConfiguration : HostingConfiguration
{
/// <summary>
/// Gets or sets a value indicating whether to always enable <c>OrchardCore.Media.Azure.Storage</c> and its
/// dependencies when hosted in Azure, for all tenants, without the ability to turn them off.
/// </summary>
public bool AlwaysEnableAzureMediaStorage { get; set; } = true;
}

0 comments on commit 2111aa6

Please sign in to comment.