@@ -25,22 +25,31 @@ namespace SEBT.Portal.Api.Composition;
2525internal static class ServiceCollectionPluginExtensions
2626{
2727 public static IServiceCollection AddPlugins ( this IServiceCollection services , IConfiguration configuration )
28- {
29- services . TryAddSingleton < IStateAuthenticationService , Defaults . DefaultStateAuthenticationService > ( ) ;
30- services . TryAddSingleton < IStateHealthCheckService , Defaults . DefaultStateHealthCheckService > ( ) ;
31- services . TryAddSingleton < ISummerEbtCaseService , Defaults . DefaultSummerEbtCaseService > ( ) ;
32- services . TryAddSingleton < IEnrollmentCheckService , Defaults . DefaultEnrollmentCheckService > ( ) ;
33- services . TryAddSingleton < IAddressUpdateService , Defaults . DefaultAddressUpdateService > ( ) ;
28+ => services . AddPlugins ( configuration , contentRootPath : null ) ;
3429
30+ /// <param name="services">The service collection.</param>
31+ /// <param name="configuration">Application configuration (must include PluginAssemblyPaths).</param>
32+ /// <param name="contentRootPath">
33+ /// <see cref="Microsoft.Extensions.Hosting.IHostEnvironment.ContentRootPath"/> so plugin folders under
34+ /// the API project directory are found when DLLs were not copied into <c>AppContext.BaseDirectory</c>.
35+ /// </param>
36+ public static IServiceCollection AddPlugins (
37+ this IServiceCollection services ,
38+ IConfiguration configuration ,
39+ string ? contentRootPath )
40+ {
3541 var healthChecksBuilder = services . AddHealthChecks ( ) ;
3642
3743 var pluginAssemblyPaths = configuration
3844 . GetSection ( "PluginAssemblyPaths" )
3945 . Get < string [ ] > ( )
4046 ?? throw new InvalidOperationException ( "PluginAssemblyPaths missing from configuration." ) ;
41- Log . Information ( "Loading plugins from: {PluginAssemblyPaths}" , pluginAssemblyPaths ) ;
47+ Log . Debug ( "Loading plugins from: {PluginAssemblyPaths}" , pluginAssemblyPaths ) ;
4248
43- var loadedAssemblies = PluginAssemblyLoader . LoadAssembliesFromPaths ( pluginAssemblyPaths ) ;
49+ var loadedAssemblies = PluginAssemblyLoader . LoadAssembliesFromPaths (
50+ pluginAssemblyPaths ,
51+ SearchOption . TopDirectoryOnly ,
52+ contentRootPath ) ;
4453
4554 var pluginTypes = loadedAssemblies
4655 . SelectMany ( a =>
@@ -57,10 +66,12 @@ public static IServiceCollection AddPlugins(this IServiceCollection services, IC
5766
5867 foreach ( var pluginType in pluginTypes )
5968 {
60- Log . Information ( "Discovered plugin type: {PluginType}" , pluginType . FullName ) ;
69+ Log . Debug ( "Discovered plugin type: {PluginType}" , pluginType . FullName ) ;
6170
71+ // Only service interfaces that extend IStatePlugin (excludes IDisposable and other
72+ // non-state contracts that appear on GetInterfaces()).
6273 var pluginInterfaces = pluginType . GetInterfaces ( )
63- . Where ( i => i != typeof ( IStatePlugin ) )
74+ . Where ( i => i != typeof ( IStatePlugin ) && typeof ( IStatePlugin ) . IsAssignableFrom ( i ) )
6475 . ToList ( ) ;
6576
6677 switch ( pluginInterfaces . Count )
@@ -98,7 +109,7 @@ public static IServiceCollection AddPlugins(this IServiceCollection services, IC
98109 // with a lazy resolve adapter.
99110 using var tempProvider = services . BuildServiceProvider ( ) ;
100111 var instance = ActivatorUtilities . CreateInstance ( tempProvider , pluginType ) ;
101- Log . Information ( "Constructed health check plugin: {PluginType}" , pluginType . FullName ) ;
112+ Log . Debug ( "Constructed health check plugin: {PluginType}" , pluginType . FullName ) ;
102113
103114 ( ( IStateHealthCheckService ) instance ) . ConfigureHealthChecks ( healthChecksBuilder ) ;
104115 services . AddSingleton ( pluginInterface , instance ) ;
@@ -113,12 +124,27 @@ public static IServiceCollection AddPlugins(this IServiceCollection services, IC
113124 {
114125 var logger = sp . GetRequiredService < ILoggerFactory > ( )
115126 . CreateLogger ( "SEBT.Portal.Api.Composition" ) ;
116- logger . LogInformation ( "Constructing plugin: {PluginType}" , capturedType . FullName ) ;
127+ logger . LogDebug ( "Constructing plugin: {PluginType}" , capturedType . FullName ) ;
117128 return ActivatorUtilities . CreateInstance ( sp , capturedType ) ;
118129 } ) ;
119130 }
120131 }
121132
133+ if ( pluginTypes . Count == 0 && loadedAssemblies . Count > 0 )
134+ {
135+ Log . Warning (
136+ "Loaded {AssemblyCount} plugin assemblies but discovered 0 IStatePlugin implementations. " +
137+ "See earlier warnings for TypeLoadException from GetExportedTypes." ,
138+ loadedAssemblies . Count ) ;
139+ }
140+
141+ // Register in-process defaults only for services no connector plugin provided.
142+ services . TryAddSingleton < IStateAuthenticationService , Defaults . DefaultStateAuthenticationService > ( ) ;
143+ services . TryAddSingleton < IStateHealthCheckService , Defaults . DefaultStateHealthCheckService > ( ) ;
144+ services . TryAddSingleton < ISummerEbtCaseService , Defaults . DefaultSummerEbtCaseService > ( ) ;
145+ services . TryAddSingleton < IEnrollmentCheckService , Defaults . DefaultEnrollmentCheckService > ( ) ;
146+ services . TryAddSingleton < IAddressUpdateService , Defaults . DefaultAddressUpdateService > ( ) ;
147+
122148 return services ;
123149 }
124150}
0 commit comments