diff --git a/.editorconfig b/.editorconfig index c3fb77d..60b9c12 100644 --- a/.editorconfig +++ b/.editorconfig @@ -308,6 +308,7 @@ dotnet_diagnostic.CA2240.severity = warning dotnet_diagnostic.CA2241.severity = warning dotnet_diagnostic.CA2242.severity = warning dotnet_diagnostic.CS1573.severity = suggestion +dotnet_diagnostic.CS1587.severity = none dotnet_diagnostic.CS1591.severity = none # CS8618: Non-nullable field is uninitialized. Consider declaring as nullable. diff --git a/samples/FeatureModulesSample.Module1/SampleModule1.cs b/samples/FeatureModulesSample.Module1/SampleModule1.cs index d50b036..c8172bf 100644 --- a/samples/FeatureModulesSample.Module1/SampleModule1.cs +++ b/samples/FeatureModulesSample.Module1/SampleModule1.cs @@ -11,7 +11,7 @@ namespace FeatureModulesSample.Module1; public class SampleModule1 : WebFeatureModule { - public override IModuleInfo? ModuleInfo { get; } = new FeatureModuleInfo("SampleModule1", "1.0.0"); + public override IModuleInfo ModuleInfo { get; } = new FeatureModuleInfo("SampleModule1", "1.0.0"); public override void MapEndpoints(WebApplication app) { diff --git a/samples/FeatureModulesSample/FeatureModule.cs b/samples/FeatureModulesSample/FeatureModule.cs index f0e16c5..b240947 100644 --- a/samples/FeatureModulesSample/FeatureModule.cs +++ b/samples/FeatureModulesSample/FeatureModule.cs @@ -4,10 +4,5 @@ namespace FeatureModulesSample; public class FeatureModule : Infinity.Toolkit.FeatureModules.FeatureModule { - public override IModuleInfo? ModuleInfo { get; } = new FeatureModuleInfo("FeatureModule", "1.0.0"); - - public override ModuleContext RegisterModule(ModuleContext moduleContext) - { - return base.RegisterModule(moduleContext); - } + public override IModuleInfo ModuleInfo { get; } = new FeatureModuleInfo("FeatureModule", "1.0.0"); } diff --git a/samples/FeatureModulesSample/WeatherModule.cs b/samples/FeatureModulesSample/WeatherModule.cs index 4ed90ef..88f33ca 100644 --- a/samples/FeatureModulesSample/WeatherModule.cs +++ b/samples/FeatureModulesSample/WeatherModule.cs @@ -4,7 +4,7 @@ namespace FeatureModulesSample; internal class WeatherModule : WebFeatureModule { - public override IModuleInfo? ModuleInfo { get; } = new FeatureModuleInfo("WeatherModule", "1.0.0"); + public override IModuleInfo ModuleInfo { get; } = new FeatureModuleInfo("WeatherModule", "1.0.0"); public override void MapEndpoints(WebApplication builder) { diff --git a/src/Infinity.Toolkit.Azure/Configuration/ConfigurationBuilderExtensions.cs b/src/Infinity.Toolkit.Azure/Configuration/ConfigurationBuilderExtensions.cs index dd1ef97..a9f540e 100644 --- a/src/Infinity.Toolkit.Azure/Configuration/ConfigurationBuilderExtensions.cs +++ b/src/Infinity.Toolkit.Azure/Configuration/ConfigurationBuilderExtensions.cs @@ -114,6 +114,19 @@ public static IHostApplicationBuilder ConfigureAzureAppConfiguration(this IHostA builder.Configuration.GetSection(configSectionName).Bind(settings); configureSettings?.Invoke(settings); + // Make sure we have a connection string or endpoint before adding Azure App Configuration + if (builder.Configuration.GetConnectionString("AzureAppConfig") is null && settings.Endpoint is null && Environment.GetEnvironmentVariable("AZURE_APP_CONFIG_ENDPOINT") is null) + { + logger.LogWarning(message: $""" + Azure App Configuration is not configured. + Please provide a valid connection string or endpoint using one of the following sources: + - 'ConnectionStrings:AzureAppConfig' (connection string) + - '{configSectionName}:Endpoint' configuration section + - 'AZURE_APP_CONFIG_ENDPOINT' environment variable + """); + return builder; + } + builder.Configuration.AddAzureAppConfiguration(options => { if (builder.Configuration.GetConnectionString("AzureAppConfig") is string connectionString) @@ -134,15 +147,7 @@ public static IHostApplicationBuilder ConfigureAzureAppConfiguration(this IHostA var envEndpoint = Environment.GetEnvironmentVariable("AZURE_APP_CONFIG_ENDPOINT"); if (!Uri.TryCreate(envEndpoint, UriKind.Absolute, out endpointUri)) { - // Log instead of throwing - logger?.LogError(message: $""" - Unable to find a valid Azure App Configuration endpoint. - Please provide a valid endpoint using one of the following sources: - - 'ConnectionStrings:AzureAppConfig' (connection string) - - '${configSectionName}:Endpoint' configuration section - - 'AZURE_APP_CONFIG_ENDPOINT' environment variable - """); - return; + logger?.LogWarning("Found invalid Azure App Configuration endpoint in environment variable 'AZURE_APP_CONFIG_ENDPOINT': {EnvEndpoint}", envEndpoint); } } diff --git a/src/Infinity.Toolkit.Azure/Infinity.Toolkit.Azure.csproj b/src/Infinity.Toolkit.Azure/Infinity.Toolkit.Azure.csproj index 1bc1e67..bb52831 100644 --- a/src/Infinity.Toolkit.Azure/Infinity.Toolkit.Azure.csproj +++ b/src/Infinity.Toolkit.Azure/Infinity.Toolkit.Azure.csproj @@ -5,7 +5,7 @@ enable enable Infinity.Toolkit.Azure - 1.0.1 + 1.0.2 diff --git a/src/Infinity.Toolkit.FeatureModules/FeatureModule.cs b/src/Infinity.Toolkit.FeatureModules/FeatureModule.cs index 251a724..3aecf64 100644 --- a/src/Infinity.Toolkit.FeatureModules/FeatureModule.cs +++ b/src/Infinity.Toolkit.FeatureModules/FeatureModule.cs @@ -5,9 +5,9 @@ /// public abstract class FeatureModule : IFeatureModule { - public abstract IModuleInfo? ModuleInfo { get; } + public abstract IModuleInfo ModuleInfo { get; } - public virtual ModuleContext RegisterModule(ModuleContext moduleContext) => moduleContext; + public virtual void RegisterModule(IHostApplicationBuilder builder) { } } /// @@ -16,7 +16,7 @@ public abstract class FeatureModule : IFeatureModule /// public abstract class WebFeatureModule : IWebFeatureModule { - public abstract IModuleInfo? ModuleInfo { get; } + public abstract IModuleInfo ModuleInfo { get; } public virtual void RegisterModule(IHostApplicationBuilder builder) { } diff --git a/src/Infinity.Toolkit.FeatureModules/IFeatureModule.cs b/src/Infinity.Toolkit.FeatureModules/IFeatureModule.cs index b97420f..6eb210b 100644 --- a/src/Infinity.Toolkit.FeatureModules/IFeatureModule.cs +++ b/src/Infinity.Toolkit.FeatureModules/IFeatureModule.cs @@ -6,7 +6,7 @@ public interface IFeatureModuleBase /// /// Gets the meta data that describes the module such as name and version. /// - IModuleInfo? ModuleInfo { get; } + IModuleInfo ModuleInfo { get; } } /// @@ -22,7 +22,7 @@ public interface IFeatureModule : IFeatureModuleBase /// /// Register all dependencies needed by a module in the DI-container. /// - ModuleContext RegisterModule(ModuleContext moduleContext); + void RegisterModule(IHostApplicationBuilder builder); } /// @@ -33,15 +33,10 @@ public interface IFeatureModule : IFeatureModuleBase /// module to independently register services and endpoints. This facilitates separation of concerns and improves /// maintainability in large applications. Modules should ensure that all required services are registered before /// mapping endpoints. -public interface IWebFeatureModule : IFeatureModuleBase +public interface IWebFeatureModule : IFeatureModule { /// /// Maps all endpoints provided by the module in the DI-container. /// void MapEndpoints(WebApplication app); - - /// - /// Register all dependencies needed by a web module in the DI-container. - /// - void RegisterModule(IHostApplicationBuilder builder); } diff --git a/src/Infinity.Toolkit.FeatureModules/WebApplicationBuilderExtensions.cs b/src/Infinity.Toolkit.FeatureModules/IHostApplicationBuilderExtensions.cs similarity index 59% rename from src/Infinity.Toolkit.FeatureModules/WebApplicationBuilderExtensions.cs rename to src/Infinity.Toolkit.FeatureModules/IHostApplicationBuilderExtensions.cs index 105e423..30e2b3e 100644 --- a/src/Infinity.Toolkit.FeatureModules/WebApplicationBuilderExtensions.cs +++ b/src/Infinity.Toolkit.FeatureModules/IHostApplicationBuilderExtensions.cs @@ -1,17 +1,18 @@ using Infinity.Toolkit.LogFormatter; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.DependencyModel; namespace Infinity.Toolkit.FeatureModules; -public static class WebApplicationBuilderExtensions +public static class IHostApplicationBuilderExtensions { private const string FeatureModulesConfigKey = "FeatureModules"; /// /// Add all feature modules that are found in the solution. /// - public static WebApplicationBuilder AddFeatureModules( - this WebApplicationBuilder builder, + public static IHostApplicationBuilder AddFeatureModules( + this IHostApplicationBuilder builder, Action configure, string configKey = FeatureModulesConfigKey, ILoggerFactory? loggerFactory = null) @@ -26,8 +27,8 @@ public static WebApplicationBuilder AddFeatureModules( /// /// Add all feature modules that are found in the solution. /// - public static WebApplicationBuilder AddFeatureModules( - this WebApplicationBuilder builder, + public static IHostApplicationBuilder AddFeatureModules( + this IHostApplicationBuilder builder, IConfiguration config) { var options = new FeatureModuleOptions(); @@ -38,12 +39,12 @@ public static WebApplicationBuilder AddFeatureModules( /// /// Add all feature modules that are found in the solution. /// - public static WebApplicationBuilder AddFeatureModules(this WebApplicationBuilder builder) + public static IHostApplicationBuilder AddFeatureModules(this IHostApplicationBuilder builder) { return builder.AddFeatureModules(options => { }); } - internal static WebApplicationBuilder RegisterFeatureModules(this WebApplicationBuilder builder, FeatureModuleOptions options, ILoggerFactory? loggerFactory) + internal static IHostApplicationBuilder RegisterFeatureModules(this IHostApplicationBuilder builder, FeatureModuleOptions options, ILoggerFactory? loggerFactory) { loggerFactory ??= LoggerFactory.Create(loggingBuilder => { @@ -60,19 +61,61 @@ internal static WebApplicationBuilder RegisterFeatureModules(this WebApplication { logger?.LogDebug(new EventId(1000, "Scanning"), "Scanning assemblies for feature modules..."); - var discoveredModules = ModuleUtilities.DiscoverModules(options, logger); + var discoveredModules = DiscoverModules(options, logger); RegisterModules(discoveredModules, builder, logger); logger?.LogDebug(new EventId(1003, "ScanningComplete"), "Registering feature modules completed."); } catch (Exception ex) { - logger.LogError(new EventId(5000, "ScanningFailed"), "Failed to register feature modules. {ex}", ex.Message); + logger?.LogError(new EventId(5000, "ScanningFailed"), "Failed to register feature modules. {ex}", ex.Message); } return builder; } + /// + /// Discover all modules that references IFeatureModule. + /// + /// A list of all feature modules in the solution. + private static IEnumerable DiscoverModules(FeatureModuleOptions options, ILogger? logger) + { + var assemblies = new HashSet + { + typeof(Assembly).Assembly, + }; + + var entryAssembly = Assembly.GetEntryAssembly(); + var context = DependencyContext.Load(entryAssembly!)!; + + foreach (var assembly in context.RuntimeLibraries) + { + if (IsReferencingCurrentAssembly(assembly, typeof(IHostApplicationBuilderExtensions).Assembly.GetName().Name)) + { + foreach (var assemblyName in assembly.GetDefaultAssemblyNames(context)) + { + assemblies.Add(Assembly.Load(assemblyName)); + } + } + } + + var typesAssignableTo = assemblies + .SelectMany(x => + x.DefinedTypes + .Where(type => type is { IsAbstract: false, IsInterface: false } && + type.IsAssignableTo(typeof(IFeatureModuleBase)) && + !options.ExcludedModules.Any(t => t == type.FullName))) + .OrderBy(c => c.FullName); + + logger?.LogInformation(new EventId(1001, "ModulesFound"), "Found {moduleCount} feature modules.", typesAssignableTo.Count()); + return typesAssignableTo; + } + + private static bool IsReferencingCurrentAssembly(Library library, string? currentAssemblyName) + { + return library.Dependencies.Any(dependency => dependency.Name.Equals(currentAssemblyName)); + } + /// /// Register all classes implementing IFeatureModule while scanning the project to IServiceCollection. /// @@ -99,20 +142,10 @@ private static void RegisterModules(IEnumerable discoveredModules, IHo { registeredFeatureModules.Add(module.GetType(), module); - if (module is IWebFeatureModule webModule) + if (module is IFeatureModule featureModule) { - logger?.LogInformation(new EventId(1002, "RegisteringModules"), "Registering web feature module: {module}", module.GetType().FullName); - webModule.RegisterModule(builder); - } - else if (module is IFeatureModule featureModule) - { - logger?.LogInformation(new EventId(1002, "RegisteringModules"), "Registering feature module: {module}", module.GetType().FullName); - featureModule?.RegisterModule(new() - { - Configuration = builder.Configuration, - Environment = builder.Environment, - Services = builder.Services - }); + logger?.LogInformation(new EventId(1002, "RegisteringModules"), "Registering feature module: {module} - v{version}", module.ModuleInfo?.Name ?? module.GetType().FullName, module.ModuleInfo?.Version ?? "1.0"); + featureModule?.RegisterModule(builder); } else { @@ -125,5 +158,4 @@ private static void RegisterModules(IEnumerable discoveredModules, IHo options.AdditionalAssemblies.AddRange([.. registeredFeatureModules.Keys.Select(x => x.Assembly)]); }); } - } diff --git a/src/Infinity.Toolkit.FeatureModules/IModuleInfo.cs b/src/Infinity.Toolkit.FeatureModules/IModuleInfo.cs index a11a754..81585ee 100644 --- a/src/Infinity.Toolkit.FeatureModules/IModuleInfo.cs +++ b/src/Infinity.Toolkit.FeatureModules/IModuleInfo.cs @@ -11,5 +11,5 @@ public class FeatureModuleInfo(string? name, string? version) : IModuleInfo { public string? Name { get; init; } = name; - public string? Version { get; init; } = version; + public string? Version { get; init; } = version ?? "1.0.0"; } diff --git a/src/Infinity.Toolkit.FeatureModules/Infinity.Toolkit.FeatureModules.csproj b/src/Infinity.Toolkit.FeatureModules/Infinity.Toolkit.FeatureModules.csproj index 12ca6b8..b7f6fc9 100644 --- a/src/Infinity.Toolkit.FeatureModules/Infinity.Toolkit.FeatureModules.csproj +++ b/src/Infinity.Toolkit.FeatureModules/Infinity.Toolkit.FeatureModules.csproj @@ -4,7 +4,7 @@ net10.0;net9.0;net8.0 enable enable - 1.3.0 + 1.4.0 Infinity.Toolkit.FeatureModules Infinity Toolkit Feature Modules let's you automatically register dependencies and endpoints in modules which simplifies development when you are working with vertical feature slices. FeatureModules;VerticalSliceArchitecture;ModularMonoliths;MinimalApis diff --git a/src/Infinity.Toolkit.FeatureModules/ModuleContext.cs b/src/Infinity.Toolkit.FeatureModules/ModuleContext.cs deleted file mode 100644 index 9716bcd..0000000 --- a/src/Infinity.Toolkit.FeatureModules/ModuleContext.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace Infinity.Toolkit.FeatureModules; - -public sealed record ModuleContext -{ - /// - /// Gets the host environment. - /// - public required IHostEnvironment Environment { get; init; } - - /// - /// Gets the service collection. - /// - public required IServiceCollection Services { get; init; } - - /// - /// Gets the configuration. - /// - public required IConfiguration Configuration { get; init; } -} diff --git a/src/Infinity.Toolkit.FeatureModules/ModuleUtilities.cs b/src/Infinity.Toolkit.FeatureModules/ModuleUtilities.cs deleted file mode 100644 index f6826ca..0000000 --- a/src/Infinity.Toolkit.FeatureModules/ModuleUtilities.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Microsoft.Extensions.DependencyModel; - -namespace Infinity.Toolkit.FeatureModules; - -internal static class ModuleUtilities -{ - - /// - /// Discover all modules that references IFeatureModule. - /// - /// A list of all feature modules in the solution. - public static IEnumerable DiscoverModules(FeatureModuleOptions options, ILogger? logger) - { - var assemblies = new HashSet - { - typeof(Assembly).Assembly, - }; - - var entryAssembly = Assembly.GetEntryAssembly(); - var context = DependencyContext.Load(entryAssembly!)!; - - foreach (var assembly in context.RuntimeLibraries) - { - if (IsReferencingCurrentAssembly(assembly, typeof(WebApplicationBuilderExtensions).Assembly.GetName().Name)) - { - foreach (var assemblyName in assembly.GetDefaultAssemblyNames(context)) - { - assemblies.Add(Assembly.Load(assemblyName)); - } - } - } - - var typesAssignableTo = assemblies - .SelectMany(x => - x.DefinedTypes - .Where(type => type is { IsAbstract: false, IsInterface: false } && - type.IsAssignableTo(typeof(IFeatureModuleBase)) && - !options.ExcludedModules.Any(t => t == type.FullName))); - - logger?.LogInformation(new EventId(1001, "ModulesFound"), "Found {moduleCount} feature modules.", typesAssignableTo.Count()); - return typesAssignableTo; - } - - private static bool IsReferencingCurrentAssembly(Library library, string? currentAssemblyName) - { - return library.Dependencies.Any(dependency => dependency.Name.Equals(currentAssemblyName)); - } -}