Browse Source

Merge branch 'abpframework:dev' into dev

pull/25227/head
Anton 6 months ago
committed by GitHub
parent
commit
442762f6d0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 20
      framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs
  2. 69
      framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs
  3. 14
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
  4. 2
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/AbpInitLogEntry.cs
  5. 1
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLogger.cs
  6. 22
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs
  7. 8
      framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLoggerFactory.cs
  8. 62
      framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/WarnForOrphanedAbpModules_Tests.cs

20
framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs

@ -20,7 +20,8 @@ public static class AbpRegistrationBuilderExtensions
ServiceDescriptor serviceDescriptor,
IModuleContainer moduleContainer,
ServiceRegistrationActionList registrationActionList,
ServiceActivatedActionList activatedActionList)
ServiceActivatedActionList activatedActionList,
HashSet<Assembly>? nonModuleAssemblies = null)
where TActivatorData : ReflectionActivatorData
{
registrationBuilder = registrationBuilder.InvokeActivatedActions(activatedActionList, serviceDescriptor);
@ -37,7 +38,7 @@ public static class AbpRegistrationBuilderExtensions
return registrationBuilder;
}
registrationBuilder = registrationBuilder.EnablePropertyInjection(moduleContainer, implementationType);
registrationBuilder = registrationBuilder.EnablePropertyInjection(moduleContainer, implementationType, nonModuleAssemblies);
registrationBuilder = registrationBuilder.InvokeRegistrationActions(registrationActionList, serviceType, implementationType, serviceDescriptor.ServiceKey);
return registrationBuilder;
@ -99,15 +100,24 @@ public static class AbpRegistrationBuilderExtensions
private static IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> EnablePropertyInjection<TLimit, TActivatorData, TRegistrationStyle>(
this IRegistrationBuilder<TLimit, TActivatorData, TRegistrationStyle> registrationBuilder,
IModuleContainer moduleContainer,
Type implementationType)
Type implementationType,
HashSet<Assembly>? nonModuleAssemblies)
where TActivatorData : ReflectionActivatorData
{
// Enable Property Injection only for types in an assembly containing an AbpModule and without a DisablePropertyInjection attribute on class or properties.
if (moduleContainer.Modules.Any(m => m.AllAssemblies.Contains(implementationType.Assembly)) &&
implementationType.GetCustomAttributes(typeof(DisablePropertyInjectionAttribute), true).IsNullOrEmpty())
if (!implementationType.GetCustomAttributes(typeof(DisablePropertyInjectionAttribute), true).IsNullOrEmpty())
{
return registrationBuilder;
}
if (moduleContainer.Modules.Any(m => m.AllAssemblies.Contains(implementationType.Assembly)))
{
registrationBuilder = registrationBuilder.PropertiesAutowired(new AbpPropertySelector(false));
}
else
{
nonModuleAssemblies?.Add(implementationType.Assembly);
}
return registrationBuilder;
}

69
framework/src/Volo.Abp.Autofac/Autofac/Extensions/DependencyInjection/AutofacRegistration.cs

@ -24,7 +24,9 @@
// OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using Autofac.Builder;
using Autofac.Core;
@ -33,7 +35,9 @@ using Autofac.Core.Activators.Delegate;
using Autofac.Core.Activators.Reflection;
using Autofac.Core.Resolving.Pipeline;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Volo.Abp;
using Volo.Abp.Autofac;
using Volo.Abp.Modularity;
namespace Autofac.Extensions.DependencyInjection;
@ -301,6 +305,10 @@ public static class AutofacRegistration
var registrationActionList = services.GetRegistrationActionList();
var activatedActionList = services.GetServiceActivatedActionList();
// Assemblies where property injection was skipped because they are not in the module chain.
// Collected in EnablePropertyInjection when a type registration's assembly is not part of any loaded module.
var nonModuleAssemblies = new HashSet<Assembly>();
foreach (var descriptor in services)
{
var implementationType = descriptor.NormalizedImplementationType();
@ -314,7 +322,7 @@ public static class AutofacRegistration
.RegisterGeneric(implementationType)
.ConfigureServiceType(descriptor)
.ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons)
.ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList);
.ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList, nonModuleAssemblies);
}
else
{
@ -322,7 +330,7 @@ public static class AutofacRegistration
.RegisterType(implementationType)
.ConfigureServiceType(descriptor)
.ConfigureLifecycle(descriptor.Lifetime, lifetimeScopeTagForSingletons)
.ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList);
.ConfigureAbpConventions(descriptor, moduleContainer, registrationActionList, activatedActionList, nonModuleAssemblies);
}
continue;
@ -374,5 +382,62 @@ public static class AutofacRegistration
.ConfigureLifecycle(descriptor.Lifetime, null)
.ExternallyOwned();
}
WarnForOrphanedAbpModules(services, moduleContainer, nonModuleAssemblies);
}
private static void WarnForOrphanedAbpModules(
IServiceCollection services,
IModuleContainer moduleContainer,
HashSet<Assembly> nonModuleAssemblies)
{
if (nonModuleAssemblies.Count == 0)
{
return;
}
var logger = services.GetInitLogger<AbpAutofacModule>();
var loadedModuleTypes = new HashSet<Type>(
moduleContainer.Modules.Select(m => m.Type));
// Only assemblies that directly reference Volo.Abp.Core can contain AbpModule subclasses.
// This skips framework/third-party assemblies (Microsoft.Extensions.*, etc.) cheaply.
var abpCoreAssemblyName = typeof(AbpModule).Assembly.GetName().Name;
foreach (var assembly in nonModuleAssemblies)
{
if (!assembly.GetReferencedAssemblies().Any(r => r.Name == abpCoreAssemblyName))
{
continue;
}
Type[] types;
try
{
types = assembly.GetTypes();
}
catch (ReflectionTypeLoadException ex)
{
types = ex.Types.Where(t => t != null).ToArray()!;
}
catch (Exception)
{
continue;
}
foreach (var type in types)
{
if (AbpModule.IsAbpModule(type) && !loadedModuleTypes.Contains(type))
{
logger.LogWarning(
$"Assembly '{assembly.GetName().Name}' has services registered in the DI container, " +
$"but its ABP module '{type.FullName}' is not in the [DependsOn] chain. " +
"Property injection (e.g. LazyServiceProvider) will not work for these types " +
"and may cause NullReferenceException at runtime. " +
$"Add typeof({type.Name}) to your module's [DependsOn] attribute to fix this.");
}
}
}
}
}

14
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs

@ -128,20 +128,24 @@ public abstract class AbpApplicationBase : IAbpApplication
protected virtual void WriteInitLogs(IServiceProvider serviceProvider)
{
var logger = serviceProvider.GetService<ILogger<AbpApplicationBase>>();
if (logger == null)
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
if (loggerFactory == null)
{
return;
}
var initLogger = serviceProvider.GetRequiredService<IInitLoggerFactory>().Create<AbpApplicationBase>();
var initLoggerFactory = serviceProvider.GetRequiredService<IInitLoggerFactory>();
foreach (var entry in initLogger.Entries)
foreach (var entry in initLoggerFactory.GetAllEntries())
{
var categoryName = string.IsNullOrEmpty(entry.CategoryName)
? nameof(AbpApplicationBase)
: entry.CategoryName;
var logger = loggerFactory.CreateLogger(categoryName);
logger.Log(entry.LogLevel, entry.EventId, entry.State, entry.Exception, entry.Formatter);
}
initLogger.Entries.Clear();
initLoggerFactory.ClearAllEntries();
}
protected virtual IReadOnlyList<IAbpModuleDescriptor> LoadModules(IServiceCollection services, AbpApplicationCreationOptions options)

2
framework/src/Volo.Abp.Core/Volo/Abp/Logging/AbpInitLogEntry.cs

@ -5,6 +5,8 @@ namespace Volo.Abp.Logging;
public class AbpInitLogEntry
{
public string CategoryName { get; set; } = default!;
public LogLevel LogLevel { get; set; }
public EventId EventId { get; set; }

1
framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLogger.cs

@ -17,6 +17,7 @@ public class DefaultInitLogger<T> : IInitLogger<T>
{
Entries.Add(new AbpInitLogEntry
{
CategoryName = typeof(T).FullName!,
LogLevel = logLevel,
EventId = eventId,
State = state!,

22
framework/src/Volo.Abp.Core/Volo/Abp/Logging/DefaultInitLoggerFactory.cs

@ -1,14 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.Logging;
public class DefaultInitLoggerFactory : IInitLoggerFactory
{
private readonly Dictionary<Type, object> _cache = [];
private readonly List<List<AbpInitLogEntry>> _entryLists = [];
public virtual IInitLogger<T> Create<T>()
{
return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () => new DefaultInitLogger<T>());
return (IInitLogger<T>)_cache.GetOrAdd(typeof(T), () =>
{
var logger = new DefaultInitLogger<T>();
_entryLists.Add(logger.Entries);
return logger;
});
}
public virtual List<AbpInitLogEntry> GetAllEntries()
{
return _entryLists.SelectMany(l => l).ToList();
}
public virtual void ClearAllEntries()
{
foreach (var list in _entryLists)
{
list.Clear();
}
}
}

8
framework/src/Volo.Abp.Core/Volo/Abp/Logging/IInitLoggerFactory.cs

@ -1,6 +1,12 @@
namespace Volo.Abp.Logging;
using System.Collections.Generic;
namespace Volo.Abp.Logging;
public interface IInitLoggerFactory
{
IInitLogger<T> Create<T>();
List<AbpInitLogEntry> GetAllEntries();
void ClearAllEntries();
}

62
framework/test/Volo.Abp.Autofac.Tests/Volo/Abp/Autofac/WarnForOrphanedAbpModules_Tests.cs

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Shouldly;
using Volo.Abp.Logging;
using Volo.Abp.Modularity;
using Volo.Abp.Testing;
using Xunit;
namespace Volo.Abp.Autofac;
public class WarnForOrphanedAbpModules_Tests : AbpIntegratedTest<WarnForOrphanedAbpModules_Tests.TestModule>
{
private static readonly Type OrphanModuleType = typeof(AbpTestModule);
private List<AbpInitLogEntry> _initLogEntries = [];
protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options)
{
options.UseAutofac();
}
protected override IServiceProvider CreateServiceProvider(IServiceCollection services)
{
var serviceProvider = base.CreateServiceProvider(services);
// Capture init log entries after Autofac Populate/Register but before WriteInitLogs clears them.
_initLogEntries = serviceProvider.GetRequiredService<IInitLoggerFactory>().GetAllEntries();
return serviceProvider;
}
[Fact]
public void Should_Warn_For_Orphaned_Abp_Modules()
{
_initLogEntries
.Where(e => e.LogLevel == LogLevel.Warning)
.ShouldContain(e => e.Message.Contains(OrphanModuleType.FullName!),
$"Expected a warning for orphaned module '{OrphanModuleType.FullName}'.");
}
[Fact]
public void Should_Not_Warn_For_Loaded_Modules()
{
_initLogEntries
.Where(e => e.LogLevel == LogLevel.Warning)
.ShouldNotContain(e => e.Message.Contains(typeof(AbpAutofacModule).FullName!),
"Modules in the [DependsOn] chain should not be reported as orphaned.");
}
[DependsOn(typeof(AbpAutofacModule))]
public class TestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Simulate ASP.NET Core's AddControllersAsServices() registering a type
// from an assembly whose ABP module is NOT in the [DependsOn] chain.
context.Services.AddTransient<AbpTestModule>();
}
}
}
Loading…
Cancel
Save