diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index d027baf..fbf7e1d 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -21,4 +21,6 @@ jobs: run: dotnet restore - name: Build run: dotnet build -c Release --no-restore + - name: Test + run: dotnet test -c Release --no-restore diff --git a/src/BuildingBlocks/MassTransit/Extensions.cs b/src/BuildingBlocks/MassTransit/Extensions.cs index e56d18a..bc5fdb9 100644 --- a/src/BuildingBlocks/MassTransit/Extensions.cs +++ b/src/BuildingBlocks/MassTransit/Extensions.cs @@ -18,8 +18,8 @@ public static class Extensions bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER"), out var inContainer) && inContainer; - public static IServiceCollection AddCustomMassTransit(this IServiceCollection services, Assembly assembly, - IWebHostEnvironment env) + public static IServiceCollection AddCustomMassTransit(this IServiceCollection services, + IWebHostEnvironment env, Assembly assembly) { services.AddOptions() .BindConfiguration(nameof(RabbitMqOptions)) @@ -29,22 +29,22 @@ public static class Extensions { services.AddMassTransitTestHarness(configure => { - SetupMasstransitConfigurations(services, assembly, configure); + SetupMasstransitConfigurations(services, configure, assembly); }); } else { services.AddMassTransit(configure => { - SetupMasstransitConfigurations(services, assembly, configure); + SetupMasstransitConfigurations(services, configure, assembly); }); } return services; } - private static void SetupMasstransitConfigurations(IServiceCollection services, Assembly assembly, - IBusRegistrationConfigurator configure) + private static void SetupMasstransitConfigurations(IServiceCollection services, + IBusRegistrationConfigurator configure, Assembly assembly) { configure.AddConsumers(assembly); @@ -60,38 +60,44 @@ public static class Extensions h.Password(rabbitMqOptions?.Password); }); - var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()) + + var integrationEventRootAssemblies = Assembly.GetAssembly(typeof(IIntegrationEvent)); + + var types = integrationEventRootAssemblies?.GetTypes() .Where(x => x.IsAssignableTo(typeof(IIntegrationEvent)) && !x.IsInterface && !x.IsAbstract - && !x.IsGenericType); + && !x.IsGenericType)?.ToList(); - foreach (var type in types) + if (types != null && types.Any()) { - var consumers = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypes()) - .Where(x => x.IsAssignableTo(typeof(IConsumer<>).MakeGenericType(type))).ToList(); + foreach (var type in types) + { + var consumers = assembly?.GetTypes() + .Where(x => x.IsAssignableTo(typeof(IConsumer<>).MakeGenericType(type))).ToList(); - if (consumers.Any()) - configurator.ReceiveEndpoint( - string.IsNullOrEmpty(rabbitMqOptions.ExchangeName) - ? type.Name.Underscore() - : $"{rabbitMqOptions.ExchangeName}_{type.Name.Underscore()}", e => - { - e.UseConsumeFilter(typeof(ConsumeFilter<>), context); //generic filter - - foreach (var consumer in consumers) + if (consumers != null && consumers.Any()) + configurator.ReceiveEndpoint( + string.IsNullOrEmpty(rabbitMqOptions.ExchangeName) + ? type.Name.Underscore() + : $"{rabbitMqOptions.ExchangeName}_{type.Name.Underscore()}", e => { - configurator.ConfigureEndpoints(context, x => x.Exclude(consumer)); - var methodInfo = typeof(DependencyInjectionReceiveEndpointExtensions) - .GetMethods() - .Where(x => x.GetParameters() - .Any(p => p.ParameterType == typeof(IServiceProvider))) - .FirstOrDefault(x => x.Name == "Consumer" && x.IsGenericMethod); + e.UseConsumeFilter(typeof(ConsumeFilter<>), context); //generic filter - var generic = methodInfo?.MakeGenericMethod(consumer); - generic?.Invoke(e, new object[] {e, context, null}); - } - }); + foreach (var consumer in consumers) + { + configurator.ConfigureEndpoints(context, x => x.Exclude(consumer)); + var methodInfo = typeof(DependencyInjectionReceiveEndpointExtensions) + .GetMethods() + .Where(x => x.GetParameters() + .Any(p => p.ParameterType == typeof(IServiceProvider))) + .FirstOrDefault(x => x.Name == "Consumer" && x.IsGenericMethod); + + var generic = methodInfo?.MakeGenericMethod(consumer); + generic?.Invoke(e, new object[] { e, context, null }); + } + }); + } } }); } diff --git a/src/BuildingBlocks/Utils/TypeProvider.cs b/src/BuildingBlocks/Utils/TypeProvider.cs index 826e490..0e3d837 100644 --- a/src/BuildingBlocks/Utils/TypeProvider.cs +++ b/src/BuildingBlocks/Utils/TypeProvider.cs @@ -1,5 +1,6 @@ using System.Reflection; using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.ApplicationParts; namespace BuildingBlocks.Utils; @@ -36,4 +37,49 @@ public static class TypeProvider return result; } + + public static IReadOnlyList GetReferencedAssemblies(Assembly? rootAssembly) + { + var visited = new HashSet(); + var queue = new Queue(); + var listResult = new List(); + + var root = rootAssembly ?? Assembly.GetEntryAssembly(); + queue.Enqueue(root); + + do + { + var asm = queue.Dequeue(); + + if (asm == null) + break; + + listResult.Add(asm); + + foreach (var reference in asm.GetReferencedAssemblies()) + { + if (!visited.Contains(reference.FullName)) + { + // Load will add assembly into the application domain of the caller. loading assemblies explicitly to AppDomain, because assemblies are loaded lazily + // https://learn.microsoft.com/en-us/dotnet/api/system.reflection.assembly.load + queue.Enqueue(Assembly.Load(reference)); + visited.Add(reference.FullName); + } + } + } while (queue.Count > 0); + + return listResult.Distinct().ToList().AsReadOnly(); + } + + public static IReadOnlyList GetApplicationPartAssemblies(Assembly rootAssembly) + { + var rootNamespace = rootAssembly.GetName().Name!.Split('.').First(); + var list = rootAssembly!.GetCustomAttributes() + .Where(x => x.AssemblyName.StartsWith(rootNamespace, StringComparison.InvariantCulture)) + .Select(name => Assembly.Load(name.AssemblyName)) + .Distinct(); + + return list.ToList().AsReadOnly(); + } + } diff --git a/src/BuildingBlocks/Web/MinimalApiExtensions.cs b/src/BuildingBlocks/Web/MinimalApiExtensions.cs index bd0ab64..cf0417a 100644 --- a/src/BuildingBlocks/Web/MinimalApiExtensions.cs +++ b/src/BuildingBlocks/Web/MinimalApiExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.AspNetCore.Builder; +using System.Reflection; +using BuildingBlocks.Utils; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Scrutor; @@ -10,10 +12,19 @@ public static class MinimalApiExtensions public static IServiceCollection AddMinimalEndpoints( this WebApplicationBuilder applicationBuilder, - ServiceLifetime lifetime = ServiceLifetime.Scoped) + ServiceLifetime lifetime = ServiceLifetime.Scoped, + params Assembly[] assemblies) { + + var scanAssemblies = assemblies.Any() + ? assemblies + : TypeProvider.GetReferencedAssemblies(Assembly.GetCallingAssembly()) + .Concat(TypeProvider.GetApplicationPartAssemblies(Assembly.GetCallingAssembly())) + .Distinct() + .ToArray(); + applicationBuilder.Services.Scan(scan => scan - .FromAssemblies(AppDomain.CurrentDomain.GetAssemblies()) + .FromAssemblies(scanAssemblies) .AddClasses(classes => classes.AssignableTo(typeof(IMinimalEndpoint))) .UsingRegistrationStrategy(RegistrationStrategy.Append) .As() diff --git a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs index 4631559..e800444 100644 --- a/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Booking/src/Booking/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -72,7 +72,7 @@ public static class InfrastructureExtensions builder.Services.AddCustomProblemDetails(); builder.Services.AddCustomMapster(typeof(BookingRoot).Assembly); builder.Services.AddCustomHealthCheck(); - builder.Services.AddCustomMassTransit(typeof(BookingRoot).Assembly, env); + builder.Services.AddCustomMassTransit(env, typeof(BookingRoot).Assembly); builder.Services.AddCustomOpenTelemetry(); builder.Services.AddTransient(); diff --git a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs index 729a253..d5497b8 100644 --- a/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Flight/src/Flight/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -78,7 +78,7 @@ public static class InfrastructureExtensions builder.Services.AddValidatorsFromAssembly(typeof(FlightRoot).Assembly); builder.Services.AddCustomMapster(typeof(FlightRoot).Assembly); builder.Services.AddHttpContextAccessor(); - builder.Services.AddCustomMassTransit(typeof(FlightRoot).Assembly, env); + builder.Services.AddCustomMassTransit(env, typeof(FlightRoot).Assembly); builder.Services.AddCustomOpenTelemetry(); builder.Services.AddCustomHealthCheck(); diff --git a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs index 9303215..d94c815 100644 --- a/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Identity/src/Identity/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -68,7 +68,7 @@ public static class InfrastructureExtensions builder.Services.AddCustomMapster(typeof(IdentityRoot).Assembly); builder.Services.AddCustomHealthCheck(); - builder.Services.AddCustomMassTransit(typeof(IdentityRoot).Assembly, env); + builder.Services.AddCustomMassTransit(env, typeof(IdentityRoot).Assembly); builder.Services.AddCustomOpenTelemetry(); SnowFlakIdGenerator.Configure(4); diff --git a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs index 28f38e9..d3dfa26 100644 --- a/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs +++ b/src/Services/Passenger/src/Passenger/Extensions/Infrastructure/InfrastructureExtensions.cs @@ -73,7 +73,7 @@ public static class InfrastructureExtensions builder.Services.AddCustomMapster(typeof(PassengerRoot).Assembly); builder.Services.AddHttpContextAccessor(); builder.Services.AddCustomHealthCheck(); - builder.Services.AddCustomMassTransit(typeof(PassengerRoot).Assembly, env); + builder.Services.AddCustomMassTransit(env, typeof(PassengerRoot).Assembly); builder.Services.AddCustomOpenTelemetry(); builder.Services.AddGrpc(options => {