Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion samples/FeatureModulesSample.Module1/SampleModule1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
7 changes: 1 addition & 6 deletions samples/FeatureModulesSample/FeatureModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
2 changes: 1 addition & 1 deletion samples/FeatureModulesSample/WeatherModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Infinity.Toolkit.Azure/Infinity.Toolkit.Azure.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Infinity.Toolkit.Azure</RootNamespace>
<VersionPrefix>1.0.1</VersionPrefix>
<VersionPrefix>1.0.2</VersionPrefix>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions src/Infinity.Toolkit.FeatureModules/FeatureModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
/// </summary>
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) { }
}

/// <summary>
Expand All @@ -16,7 +16,7 @@ public abstract class FeatureModule : IFeatureModule
/// </summary>
public abstract class WebFeatureModule : IWebFeatureModule
{
public abstract IModuleInfo? ModuleInfo { get; }
public abstract IModuleInfo ModuleInfo { get; }

public virtual void RegisterModule(IHostApplicationBuilder builder) { }

Expand Down
11 changes: 3 additions & 8 deletions src/Infinity.Toolkit.FeatureModules/IFeatureModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public interface IFeatureModuleBase
/// <summary>
/// Gets the meta data that describes the module such as name and version.
/// </summary>
IModuleInfo? ModuleInfo { get; }
IModuleInfo ModuleInfo { get; }
}

/// <summary>
Expand All @@ -22,7 +22,7 @@ public interface IFeatureModule : IFeatureModuleBase
/// <summary>
/// Register all dependencies needed by a module in the DI-container.
/// </summary>
ModuleContext RegisterModule(ModuleContext moduleContext);
void RegisterModule(IHostApplicationBuilder builder);
}

/// <summary>
Expand All @@ -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.</remarks>
public interface IWebFeatureModule : IFeatureModuleBase
public interface IWebFeatureModule : IFeatureModule
{
/// <summary>
/// Maps all endpoints provided by the module in the DI-container.
/// </summary>
void MapEndpoints(WebApplication app);

/// <summary>
/// Register all dependencies needed by a web module in the DI-container.
/// </summary>
void RegisterModule(IHostApplicationBuilder builder);
}
Original file line number Diff line number Diff line change
@@ -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";

/// <summary>
/// Add all feature modules that are found in the solution.
/// </summary>
public static WebApplicationBuilder AddFeatureModules(
this WebApplicationBuilder builder,
public static IHostApplicationBuilder AddFeatureModules(
this IHostApplicationBuilder builder,
Action<FeatureModuleOptions> configure,
string configKey = FeatureModulesConfigKey,
ILoggerFactory? loggerFactory = null)
Expand All @@ -26,8 +27,8 @@ public static WebApplicationBuilder AddFeatureModules(
/// <summary>
/// Add all feature modules that are found in the solution.
/// </summary>
public static WebApplicationBuilder AddFeatureModules(
this WebApplicationBuilder builder,
public static IHostApplicationBuilder AddFeatureModules(
this IHostApplicationBuilder builder,
IConfiguration config)
{
var options = new FeatureModuleOptions();
Expand All @@ -38,12 +39,12 @@ public static WebApplicationBuilder AddFeatureModules(
/// <summary>
/// Add all feature modules that are found in the solution.
/// </summary>
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 =>
{
Expand All @@ -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;
}

/// <summary>
/// Discover all modules that references IFeatureModule.
/// </summary>
/// <returns>A list of all feature modules in the solution.</returns>
private static IEnumerable<TypeInfo> DiscoverModules(FeatureModuleOptions options, ILogger? logger)
{
var assemblies = new HashSet<Assembly>
{
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));
}

/// <summary>
/// Register all classes implementing IFeatureModule while scanning the project to IServiceCollection.
/// </summary>
Expand All @@ -99,20 +142,10 @@ private static void RegisterModules(IEnumerable<TypeInfo> 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
{
Expand All @@ -125,5 +158,4 @@ private static void RegisterModules(IEnumerable<TypeInfo> discoveredModules, IHo
options.AdditionalAssemblies.AddRange([.. registeredFeatureModules.Keys.Select(x => x.Assembly)]);
});
}

}
2 changes: 1 addition & 1 deletion src/Infinity.Toolkit.FeatureModules/IModuleInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TargetFrameworks>net10.0;net9.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<VersionPrefix>1.3.0</VersionPrefix>
<VersionPrefix>1.4.0</VersionPrefix>
<PackageId>Infinity.Toolkit.FeatureModules</PackageId>
<Description>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.</Description>
<PackageTags>FeatureModules;VerticalSliceArchitecture;ModularMonoliths;MinimalApis</PackageTags>
Expand Down
19 changes: 0 additions & 19 deletions src/Infinity.Toolkit.FeatureModules/ModuleContext.cs

This file was deleted.

48 changes: 0 additions & 48 deletions src/Infinity.Toolkit.FeatureModules/ModuleUtilities.cs

This file was deleted.