From f80de457abbb5c9dc45ae6bbf7a11cb86e1e7400 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Sun, 19 Jul 2026 22:55:08 +0700 Subject: [PATCH 1/7] Replace StructureMap IoC container with Microsoft.Extensions.DependencyI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swap `structuremap.patched` for `Microsoft.Extensions.DependencyInjection` 8.x - Rename `StructureMapServiceLocator` → `MediServiceLocator`; no public API change - Use explicit factory lambdas for all registrations (compile-time checked) - Dispose service locator via `IDisposable` instead of concrete type cast --- CHANGELOG.md | 1 + src/SIL.LCModel/ILcmServiceLocator.cs | 4 +- .../IOC/LcmServiceLocatorFactory.cs | 305 +++++++++--------- .../Impl/CmObjectIdentityMap.cs | 4 +- .../LcmCacheIDisposableInterfaceImpl.cs | 7 +- .../LcmServiceLocatorBootstrapper.vm.cs | 25 +- src/SIL.LCModel/SIL.LCModel.csproj | 2 +- .../DomainServices/PhonologyServicesTest.cs | 1 - 8 files changed, 166 insertions(+), 183 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8b4bb61e..417c02154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Changed +- [SIL.LCModel] Replaced the internal StructureMap (`structuremap.patched`) IoC container with `Microsoft.Extensions.DependencyInjection` (8.x). No public API change. - [SIL.LCModel] `FileUtils.IsFileUriOrPath` checks for the presence of "file:" rather than the absence of known non-file URI schemes - Changed to target .Net Framework 4.6.2 instead of 4.6.1 - Update libPalaso dependency from version 14.2.0-* to 17.0.0-* diff --git a/src/SIL.LCModel/ILcmServiceLocator.cs b/src/SIL.LCModel/ILcmServiceLocator.cs index 9d37345dd..13d982ad0 100644 --- a/src/SIL.LCModel/ILcmServiceLocator.cs +++ b/src/SIL.LCModel/ILcmServiceLocator.cs @@ -117,9 +117,9 @@ public static TService GetInstance(this IServiceProvider provider) public static IEnumerable GetAllInstances(this IServiceProvider provider) { - //structure map might not work the same way as the standard service provider, so we need to handle it separately. + // A CommonServiceLocator-based provider exposes GetAllInstances directly. if (provider is IServiceLocator serviceLocator) return serviceLocator.GetAllInstances(); - //the standard service provider handles listing all services like this, however that might not work the same in structure map if an IEnumerable is explicitly registered. + // A plain IServiceProvider resolves all registrations as an IEnumerable. return (IEnumerable) provider.GetService(typeof(IEnumerable)); } } diff --git a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs index 2290785ae..b4b9a3709 100644 --- a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs +++ b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs @@ -8,6 +8,7 @@ using System.IO; using System.Runtime.InteropServices; using CommonServiceLocator; +using Microsoft.Extensions.DependencyInjection; using SIL.LCModel.Application; using SIL.LCModel.Application.Impl; using SIL.LCModel.Core.KernelInterfaces; @@ -18,8 +19,6 @@ using SIL.LCModel.Infrastructure; using SIL.LCModel.Infrastructure.Impl; using SIL.Reporting; -using StructureMap; -using StructureMap.Pipeline; namespace SIL.LCModel.IOC { @@ -65,77 +64,52 @@ public IServiceProvider CreateServiceLocator() { logger = new FileTransactionLogger(Path.Combine(logPath, $"lcm_transaction.{DateTime.Now.Ticks}.log")); } - // NOTE: When creating an object through IServiceLocator.GetInstance the caller has - // to call Dispose() on the newly created object, unless it's a singleton - // (registered with LifecycleIs(new SingletonLifecycle())) in which case - // the Registry will dispose the object. - var registry = new Registry(); - // NB: Default is: - // .CacheBy(InstanceScope.PerRequest); + // All registrations are explicit factory lambdas that call the (often internal) + // constructors directly. This is legal because all registration code lives inside + // SIL.LCModel, and it makes the whole object graph compile-time checked: a changed + // constructor breaks the build here instead of failing at runtime resolution. + var services = new ServiceCollection(); // Add data migration manager. (new one per request) - registry - .For() - .Use(); + services.AddTransient(sp => new LcmDataMigrationManager()); // Add HomographConfiguration - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new HomographConfiguration()); // Add LcmCache - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new LcmCache()); + // Add IParagraphCounterRepository - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => + new ParagraphCounterRepository(sp.GetRequiredService())); // Add MDC - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new LcmMetaDataCache()); // Register its other interface. - registry - .For() - .Use(c => (IFwMetaDataCacheManagedInternal)c.GetInstance()); + services.AddTransient(sp => + (IFwMetaDataCacheManagedInternal)sp.GetRequiredService()); // Add Virtuals - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => + new Virtuals(sp.GetRequiredService())); // Add IdentityMap - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => + new IdentityMap(sp.GetRequiredService())); // Register IdentityMap's other interface. - registry - .For() - .Use(c => (ICmObjectIdFactory)c.GetInstance()); - registry - .For() - .Use(c => (ICmObjectRepositoryInternal)c.GetInstance()); + services.AddTransient(sp => + (ICmObjectIdFactory)sp.GetRequiredService()); + services.AddTransient(sp => + (ICmObjectRepositoryInternal)sp.GetRequiredService()); // Add surrogate factory (internal); - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => + new CmObjectSurrogateFactory(sp.GetRequiredService())); // Add surrogate repository (internal); - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => + new CmObjectSurrogateRepository(sp.GetRequiredService())); // Add BEP. switch (m_backendProviderType) @@ -143,147 +117,154 @@ public IServiceProvider CreateServiceLocator() default: throw new InvalidOperationException(Strings.ksInvalidBackendProviderType); case BackendProviderType.kXML: - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new XMLBackendProvider( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService())); break; case BackendProviderType.kMemoryOnly: - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new MemoryOnlyBackendProvider( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService())); break; case BackendProviderType.kSharedXML: - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new SharedXMLBackendProvider( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService())); break; } // Register two additional interfaces of the BEP, which are injected into other services. - registry - .For() - .Use(c => (IDataStorer)c.GetInstance()); - registry - .For() - .Use(c => (IDataReader)c.GetInstance()); + services.AddTransient(sp => (IDataStorer)sp.GetRequiredService()); + services.AddTransient(sp => (IDataReader)sp.GetRequiredService()); // Add Mediator - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new UnitOfWorkService( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + logger)); // Register additional interfaces for the UnitOfWorkService. - registry - .For() - .Use(c => (ISilDataAccessHelperInternal)c.GetInstance()); - registry - .For() - .Use(c => ((UnitOfWorkService)c.GetInstance()).ActiveUndoStack); - registry - .For() - .Use(c => (IWorkerThreadReadHandler)c.GetInstance()); - registry - .For() - .Use(c => (IUndoStackManager)c.GetInstance()); - registry.For().Use(() => logger); + services.AddTransient(sp => + (ISilDataAccessHelperInternal)sp.GetRequiredService()); + // IActionHandler is deliberately transient: it returns the current ActiveUndoStack, + // which changes over the life of the UnitOfWorkService, so it must be re-evaluated + // on every resolution. + services.AddTransient(sp => + ((UnitOfWorkService)sp.GetRequiredService()).ActiveUndoStack); + services.AddTransient(sp => + (IWorkerThreadReadHandler)sp.GetRequiredService()); + services.AddTransient(sp => + (IUndoStackManager)sp.GetRequiredService()); + if (logger != null) + services.AddSingleton(logger); + // Add generated factories. - AddFactories(registry); + AddFactories(services); // Add generated Repositories - AddRepositories(registry); + AddRepositories(services); // Add IAnalysisRepository - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new AnalysisRepository( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService())); // Add ReferenceAdjusterService - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new ReferenceAdjusterService()); // Add SDA - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new DomainDataByFlid( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService())); // Add loader helper - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new LoadingServices( + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService(), + sp.GetRequiredService())); + + // StTxtParaBldr is a stateful builder resolved by its concrete type. StructureMap + // auto-built it per request; register it transient to preserve that behavior. + services.AddTransient(sp => + new StTxtParaBldr(sp.GetRequiredService())); // Add writing system manager - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(() => new WritingSystemManager {TemplateFolder = m_dirs.TemplateDirectory}); - registry - .For() - .Use(c => (ILgWritingSystemFactory)c.GetInstance()); - - registry - .For() - .Use(c => c.GetInstance().Singleton); - - registry - .For() - .Use(m_ui); - - registry - .For() - .Use(m_dirs); - - registry - .For() - .Use(m_settings); + services.AddSingleton(sp => + new WritingSystemManager {TemplateFolder = m_dirs.TemplateDirectory}); + services.AddTransient(sp => + (ILgWritingSystemFactory)sp.GetRequiredService()); + + services.AddTransient(sp => + sp.GetRequiredService().Singleton); + + services.AddSingleton(m_ui); + + services.AddSingleton(m_dirs); + + services.AddSingleton(m_settings); // ================================================================================= - // Don't add COM object to the registry. StructureMap does not properly release COM - // objects when the container is disposed, it will crash when the container is - // disposed. + // Don't add COM objects to the container. The container does not properly release + // COM objects when it is disposed; it will crash when the container is disposed. // ================================================================================= - var container = new Container(registry); - // Do this once after something is added, to make sure - // the entire set of objects can be created. - // After it proves ok, then block the line again, - // and let SM create them 'on demand'. - //container.AssertConfigurationIsValid(); + var serviceProvider = services.BuildServiceProvider(); - return new StructureMapServiceLocator(container); + return new MediServiceLocator(serviceProvider); } } /// - /// Implementation of StructureMapServiceLocator, with extra methods of ILcmServiceLocator. + /// Service locator backed by Microsoft.Extensions.DependencyInjection, exposing the extra + /// methods of ILcmServiceLocator. It continues to implement CommonServiceLocator's + /// so downstream code that binds GetInstance<T> + /// to the interface method keeps working, source- and binary-compatible. /// - /// This class used to be named StructureMapServiceLocatorWrapper, wrapping a class - /// StructureMapServiceLocator implemented in StructureMapAdapter.dll. However, no one - /// could remember where that originally came from, and it implements only two simple methods - /// so that it seemed worth to remove the dll and implement the methods in here. - internal sealed class StructureMapServiceLocator : ServiceLocatorImplBase, + internal sealed class MediServiceLocator : ServiceLocatorImplBase, ILcmServiceLocator, IServiceLocatorInternal, IDisposable { - private Container m_container; + private ServiceProvider m_serviceProvider; /// /// Constructor /// - internal StructureMapServiceLocator(Container container) + internal MediServiceLocator(ServiceProvider serviceProvider) { - m_container = container; + m_serviceProvider = serviceProvider; } #region Disposable stuff #if DEBUG /// - ~StructureMapServiceLocator() + ~MediServiceLocator() { Dispose(false); } @@ -314,21 +295,21 @@ private void Dispose(bool fDisposing) if (fDisposing && !IsDisposed) { // dispose managed and unmanaged objects - if(m_container != null) + if (m_serviceProvider != null) { try { - m_container.Dispose(); + m_serviceProvider.Dispose(); } - catch(InvalidComObjectException e) // Intermittantly the dispose of the container fails because a COM object has become invalid + catch (InvalidComObjectException e) // Intermittantly the dispose of the container fails because a COM object has become invalid { // Display an indication of the failure, but don't crash, we made a good faith effort to dispose all our COM objects // and they probably were disposed. Also at this point we are probably shutting down, or wrapping up a unit test. - Debug.WriteLine(String.Format(@"COM problem when disposing container in StructureMapServiceLocator: {0}", e.Message)); + Debug.WriteLine(String.Format(@"COM problem when disposing container in MediServiceLocator: {0}", e.Message)); } } } - m_container = null; + m_serviceProvider = null; IsDisposed = true; } #endregion @@ -338,18 +319,16 @@ private void Dispose(bool fDisposing) /// When implemented by inheriting classes, this method will do the actual work of resolving /// the requested service instance. /// - /// Type of instance requested.B + /// Type of instance requested. /// Name of registered service you want. May be null. /// /// The requested service instance. /// protected override object DoGetInstance(Type serviceType, string key) { - if (string.IsNullOrEmpty(key)) - { - return m_container.GetInstance(serviceType); - } - return m_container.GetInstance(serviceType, key); + // LCM does not use named instances; resolve strictly by type. GetRequiredService + // preserves the throw-on-missing semantics of the previous StructureMap container. + return m_serviceProvider.GetRequiredService(serviceType); } /// @@ -362,7 +341,11 @@ protected override object DoGetInstance(Type serviceType, string key) /// protected override IEnumerable DoGetAllInstances(Type serviceType) { - foreach (object obj in m_container.GetAllInstances(serviceType)) + var enumerableType = typeof(IEnumerable<>).MakeGenericType(serviceType); + var instances = (IEnumerable)m_serviceProvider.GetService(enumerableType); + if (instances == null) + yield break; + foreach (object obj in instances) { yield return obj; } diff --git a/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs b/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs index 79b794b3c..1f54603c6 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs @@ -82,8 +82,8 @@ public bool IsDisposed /// ------------------------------------------------------------------------------------ /// - /// Releases the memory stored in the IdentityMap. This is called automatically from - /// StructureMap when the StructureMap container is disposed. + /// Releases the memory stored in the IdentityMap. This is called automatically by the + /// DI container when the container is disposed. /// /// ------------------------------------------------------------------------------------ public void Dispose() diff --git a/src/SIL.LCModel/LcmCacheIDisposableInterfaceImpl.cs b/src/SIL.LCModel/LcmCacheIDisposableInterfaceImpl.cs index dfac45f46..a528949ad 100644 --- a/src/SIL.LCModel/LcmCacheIDisposableInterfaceImpl.cs +++ b/src/SIL.LCModel/LcmCacheIDisposableInterfaceImpl.cs @@ -3,7 +3,6 @@ // (http://www.gnu.org/licenses/lgpl-2.1.html) using System; -using SIL.LCModel.IOC; namespace SIL.LCModel { @@ -128,9 +127,9 @@ private void Dispose(bool disposing) // NOTE: this needs to be last since it calls LcmCache.Dispose() which // sets all member variables to null. // This will also dispose all Singletons which includes m_serviceLocator.GetInstance() - var serviceLocatorWrapper = m_serviceLocator as StructureMapServiceLocator; - if (serviceLocatorWrapper != null) - serviceLocatorWrapper.Dispose(); + var disposableServiceLocator = m_serviceLocator as IDisposable; + if (disposableServiceLocator != null) + disposableServiceLocator.Dispose(); } // Dispose unmanaged resources here, whether disposing is true or false. diff --git a/src/SIL.LCModel/LcmGenerate/LcmServiceLocatorBootstrapper.vm.cs b/src/SIL.LCModel/LcmGenerate/LcmServiceLocatorBootstrapper.vm.cs index 3446ecf24..531316330 100644 --- a/src/SIL.LCModel/LcmGenerate/LcmServiceLocatorBootstrapper.vm.cs +++ b/src/SIL.LCModel/LcmGenerate/LcmServiceLocatorBootstrapper.vm.cs @@ -7,10 +7,10 @@ ## This file is used by the LcmGenerate task to generate the source code from the XMI ## database model. ## -------------------------------------------------------------------------------------------- +using Microsoft.Extensions.DependencyInjection; using SIL.LCModel.DomainImpl; +using SIL.LCModel.Infrastructure; using SIL.LCModel.Infrastructure.Impl; -using StructureMap; -using StructureMap.Pipeline; namespace SIL.LCModel.IOC { @@ -19,7 +19,11 @@ namespace SIL.LCModel.IOC /// internal partial class LcmServiceLocatorFactory { - private static void AddFactories(Registry registry) + // Each type is registered by its concrete type (the actual singleton) with the + // interface registered as an alias resolving to that same singleton. This mirrors the + // previous StructureMap container, which allowed resolving a registered service by + // either its interface or its concrete implementation type. + private static void AddFactories(IServiceCollection services) { #foreach($module in $lcmgenerate.Modules) #foreach($class in $module.Classes) @@ -29,23 +33,20 @@ private static void AddFactories(Registry registry) #set( $classSfx = "Factory" ) #end #if(!$class.IsAbstract) - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use<${class.Name}$classSfx>(); + services.AddSingleton<${class.Name}$classSfx>(sp => new ${class.Name}$classSfx(sp.GetRequiredService())); + services.AddSingleton(sp => sp.GetRequiredService<${class.Name}$classSfx>()); #end #end #end } - private static void AddRepositories(Registry registry) + private static void AddRepositories(IServiceCollection services) { #foreach($module in $lcmgenerate.Modules) #foreach($class in $module.Classes) - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use<${class.Name}Repository>(); + services.AddSingleton<${class.Name}Repository>(sp => new ${class.Name}Repository( + sp.GetRequiredService(), sp.GetRequiredService())); + services.AddSingleton(sp => sp.GetRequiredService<${class.Name}Repository>()); #end #end } diff --git a/src/SIL.LCModel/SIL.LCModel.csproj b/src/SIL.LCModel/SIL.LCModel.csproj index 23d125937..d7305f458 100644 --- a/src/SIL.LCModel/SIL.LCModel.csproj +++ b/src/SIL.LCModel/SIL.LCModel.csproj @@ -18,6 +18,7 @@ + @@ -26,7 +27,6 @@ - all diff --git a/tests/SIL.LCModel.Tests/DomainServices/PhonologyServicesTest.cs b/tests/SIL.LCModel.Tests/DomainServices/PhonologyServicesTest.cs index b27d66291..e77521d72 100644 --- a/tests/SIL.LCModel.Tests/DomainServices/PhonologyServicesTest.cs +++ b/tests/SIL.LCModel.Tests/DomainServices/PhonologyServicesTest.cs @@ -6,7 +6,6 @@ using SIL.LCModel.Infrastructure; using System.IO; using SIL.LCModel.Core.KernelInterfaces; -using StructureMap.Diagnostics.TreeView; using SIL.LCModel.Core.Text; using System.Xml.Linq; using System.Security.Cryptography; From 3995776ef49cbe5e0ba3a77924d261ef667efbb7 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 20 Jul 2026 10:36:08 +0700 Subject: [PATCH 2/7] rewrite some registrations to use DI --- .../Application/Impl/DomainDataByFlid.cs | 2 +- .../IOC/LcmServiceLocatorFactory.cs | 80 +++---------------- .../Impl/CmObjectIdentityMap.cs | 2 +- .../Impl/MemoryOnlyBackendProvider.cs | 2 +- .../Impl/PargraphCounterRepository.cs | 2 +- .../Impl/RepositoryAdditions.cs | 2 +- .../Impl/SharedXMLBackendProvider.cs | 2 +- .../Infrastructure/Impl/UnitOfWorkService.cs | 2 +- .../Infrastructure/Impl/XMLBackendProvider.cs | 2 +- src/SIL.LCModel/Infrastructure/XmlServices.cs | 2 +- 10 files changed, 22 insertions(+), 76 deletions(-) diff --git a/src/SIL.LCModel/Application/Impl/DomainDataByFlid.cs b/src/SIL.LCModel/Application/Impl/DomainDataByFlid.cs index 5b9944ade..d46fa79c0 100644 --- a/src/SIL.LCModel/Application/Impl/DomainDataByFlid.cs +++ b/src/SIL.LCModel/Application/Impl/DomainDataByFlid.cs @@ -42,7 +42,7 @@ internal sealed class DomainDataByFlid : ISilDataAccessManaged /// Therefore, one should not use them for multi-session identity. /// CmObject identity can only be guaranteed by using their Guids (or using '==' in code). /// - internal DomainDataByFlid(ICmObjectRepository cmObjectRepository, IStTextRepository stTxtRepository, + public DomainDataByFlid(ICmObjectRepository cmObjectRepository, IStTextRepository stTxtRepository, IFwMetaDataCacheManaged mdc, ISilDataAccessHelperInternal uowService, ILgWritingSystemFactory wsf) { diff --git a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs index b4b9a3709..81cc7400f 100644 --- a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs +++ b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using CommonServiceLocator; using Microsoft.Extensions.DependencyInjection; @@ -18,7 +19,6 @@ using SIL.LCModel.DomainServices.DataMigration; using SIL.LCModel.Infrastructure; using SIL.LCModel.Infrastructure.Impl; -using SIL.Reporting; namespace SIL.LCModel.IOC { @@ -81,8 +81,7 @@ public IServiceProvider CreateServiceLocator() services.AddSingleton(sp => new LcmCache()); // Add IParagraphCounterRepository - services.AddSingleton(sp => - new ParagraphCounterRepository(sp.GetRequiredService())); + services.AddSingleton(); // Add MDC services.AddSingleton(sp => new LcmMetaDataCache()); @@ -95,8 +94,7 @@ public IServiceProvider CreateServiceLocator() new Virtuals(sp.GetRequiredService())); // Add IdentityMap - services.AddSingleton(sp => - new IdentityMap(sp.GetRequiredService())); + services.AddSingleton(); // Register IdentityMap's other interface. services.AddTransient(sp => (ICmObjectIdFactory)sp.GetRequiredService()); @@ -117,37 +115,13 @@ public IServiceProvider CreateServiceLocator() default: throw new InvalidOperationException(Strings.ksInvalidBackendProviderType); case BackendProviderType.kXML: - services.AddSingleton(sp => new XMLBackendProvider( - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService())); + services.AddSingleton(); break; case BackendProviderType.kMemoryOnly: - services.AddSingleton(sp => new MemoryOnlyBackendProvider( - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService())); + services.AddSingleton(); break; case BackendProviderType.kSharedXML: - services.AddSingleton(sp => new SharedXMLBackendProvider( - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService())); + services.AddSingleton(); break; } // Register two additional interfaces of the BEP, which are injected into other services. @@ -155,12 +129,7 @@ public IServiceProvider CreateServiceLocator() services.AddTransient(sp => (IDataReader)sp.GetRequiredService()); // Add Mediator - services.AddSingleton(sp => new UnitOfWorkService( - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - logger)); + services.AddSingleton(); // Register additional interfaces for the UnitOfWorkService. services.AddTransient(sp => (ISilDataAccessHelperInternal)sp.GetRequiredService()); @@ -183,38 +152,20 @@ public IServiceProvider CreateServiceLocator() AddRepositories(services); // Add IAnalysisRepository - services.AddSingleton(sp => new AnalysisRepository( - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService())); + services.AddSingleton(); // Add ReferenceAdjusterService services.AddSingleton(sp => new ReferenceAdjusterService()); // Add SDA - services.AddSingleton(sp => new DomainDataByFlid( - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService())); + services.AddSingleton(); // Add loader helper - services.AddSingleton(sp => new LoadingServices( - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService())); + services.AddSingleton(); // StTxtParaBldr is a stateful builder resolved by its concrete type. StructureMap // auto-built it per request; register it transient to preserve that behavior. - services.AddTransient(sp => - new StTxtParaBldr(sp.GetRequiredService())); + services.AddTransient(); // Add writing system manager services.AddSingleton(sp => @@ -342,13 +293,8 @@ protected override object DoGetInstance(Type serviceType, string key) protected override IEnumerable DoGetAllInstances(Type serviceType) { var enumerableType = typeof(IEnumerable<>).MakeGenericType(serviceType); - var instances = (IEnumerable)m_serviceProvider.GetService(enumerableType); - if (instances == null) - yield break; - foreach (object obj in instances) - { - yield return obj; - } + var instances = (IEnumerable?)m_serviceProvider.GetServices(enumerableType); + return instances ?? Enumerable.Empty(); } #endregion diff --git a/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs b/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs index 1f54603c6..d7b95b63e 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs @@ -55,7 +55,7 @@ internal sealed class IdentityMap : ICmObjectIdFactory, IDisposable /// /// Constructor /// - internal IdentityMap(IFwMetaDataCacheManaged mdc) + public IdentityMap(IFwMetaDataCacheManaged mdc) { if (mdc == null) throw new ArgumentNullException("mdc"); diff --git a/src/SIL.LCModel/Infrastructure/Impl/MemoryOnlyBackendProvider.cs b/src/SIL.LCModel/Infrastructure/Impl/MemoryOnlyBackendProvider.cs index ca6d38f0d..0c031a624 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/MemoryOnlyBackendProvider.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/MemoryOnlyBackendProvider.cs @@ -20,7 +20,7 @@ internal sealed class MemoryOnlyBackendProvider : BackendProvider /// /// Constructor. /// - internal MemoryOnlyBackendProvider(LcmCache cache, IdentityMap identityMap, ICmObjectSurrogateFactory surrogateFactory, + public MemoryOnlyBackendProvider(LcmCache cache, IdentityMap identityMap, ICmObjectSurrogateFactory surrogateFactory, IFwMetaDataCacheManagedInternal mdc, IDataMigrationManager dataMigrationManager, ILcmUI ui, ILcmDirectories dirs, LcmSettings settings) : base(cache, identityMap, surrogateFactory, mdc, dataMigrationManager, ui, dirs, settings) { diff --git a/src/SIL.LCModel/Infrastructure/Impl/PargraphCounterRepository.cs b/src/SIL.LCModel/Infrastructure/Impl/PargraphCounterRepository.cs index cd20b4c0b..beb47d888 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/PargraphCounterRepository.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/PargraphCounterRepository.cs @@ -28,7 +28,7 @@ internal class ParagraphCounterRepository : IParagraphCounterRepository /// /// The cache. /// ------------------------------------------------------------------------------------ - internal ParagraphCounterRepository(LcmCache cache) + public ParagraphCounterRepository(LcmCache cache) { m_cache = cache; } diff --git a/src/SIL.LCModel/Infrastructure/Impl/RepositoryAdditions.cs b/src/SIL.LCModel/Infrastructure/Impl/RepositoryAdditions.cs index 5356490a7..956fe5985 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/RepositoryAdditions.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/RepositoryAdditions.cs @@ -44,7 +44,7 @@ internal class AnalysisRepository : IAnalysisRepository /// /// Constructor /// - internal AnalysisRepository(ICmObjectRepository everythingRepos, IWfiWordformRepository wordformRepos, + public AnalysisRepository(ICmObjectRepository everythingRepos, IWfiWordformRepository wordformRepos, IPunctuationFormRepository punctFormRepos, IWfiAnalysisRepository analysisRepos, IWfiGlossRepository glossRepos) { if (everythingRepos == null) throw new ArgumentNullException("everythingRepos"); diff --git a/src/SIL.LCModel/Infrastructure/Impl/SharedXMLBackendProvider.cs b/src/SIL.LCModel/Infrastructure/Impl/SharedXMLBackendProvider.cs index 65ece627a..105acbc5b 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/SharedXMLBackendProvider.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/SharedXMLBackendProvider.cs @@ -38,7 +38,7 @@ internal class SharedXMLBackendProvider : XMLBackendProvider private readonly Dictionary m_peerProcesses; private string m_commitLogDir; - internal SharedXMLBackendProvider(LcmCache cache, IdentityMap identityMap, ICmObjectSurrogateFactory surrogateFactory, IFwMetaDataCacheManagedInternal mdc, + public SharedXMLBackendProvider(LcmCache cache, IdentityMap identityMap, ICmObjectSurrogateFactory surrogateFactory, IFwMetaDataCacheManagedInternal mdc, IDataMigrationManager dataMigrationManager, ILcmUI ui, ILcmDirectories dirs, LcmSettings settings) : base(cache, identityMap, surrogateFactory, mdc, dataMigrationManager, ui, dirs, settings) { diff --git a/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs b/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs index 0da54f4be..c3921a236 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs @@ -153,7 +153,7 @@ internal BusinessTransactionState CurrentProcessingState { /// /// Constructor. /// - internal UnitOfWorkService(IDataStorer dataStorer, IdentityMap identityMap, ICmObjectRepositoryInternal objectRepository, ILcmUI ui, ITransactionLogger logger) + public UnitOfWorkService(IDataStorer dataStorer, IdentityMap identityMap, ICmObjectRepositoryInternal objectRepository, ILcmUI ui, ITransactionLogger logger) { if (dataStorer == null) throw new ArgumentNullException("dataStorer"); if (identityMap == null) throw new ArgumentNullException("identityMap"); diff --git a/src/SIL.LCModel/Infrastructure/Impl/XMLBackendProvider.cs b/src/SIL.LCModel/Infrastructure/Impl/XMLBackendProvider.cs index 1e85a1b68..677aa6959 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/XMLBackendProvider.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/XMLBackendProvider.cs @@ -121,7 +121,7 @@ public void Combine(CommitWork work) /// /// Constructor. /// - internal XMLBackendProvider(LcmCache cache, IdentityMap identityMap, + public XMLBackendProvider(LcmCache cache, IdentityMap identityMap, ICmObjectSurrogateFactory surrogateFactory, IFwMetaDataCacheManagedInternal mdc, IDataMigrationManager dataMigrationManager, ILcmUI ui, ILcmDirectories dirs, LcmSettings settings) : base(cache, identityMap, surrogateFactory, mdc, dataMigrationManager, ui, dirs, settings) diff --git a/src/SIL.LCModel/Infrastructure/XmlServices.cs b/src/SIL.LCModel/Infrastructure/XmlServices.cs index 41874214c..e45e30e30 100644 --- a/src/SIL.LCModel/Infrastructure/XmlServices.cs +++ b/src/SIL.LCModel/Infrastructure/XmlServices.cs @@ -168,7 +168,7 @@ internal class LoadingServices /// /// Constructor /// - internal LoadingServices(IDataSetup dataSetup, ICmObjectIdFactory objIdFactory, + public LoadingServices(IDataSetup dataSetup, ICmObjectIdFactory objIdFactory, IFwMetaDataCacheManaged mdcManaged, ILgWritingSystemFactory wsf, IUnitOfWorkService uowService, ICmObjectSurrogateRepository surrRepository, ICmObjectRepository cmObjRepository) { From bc96836699db37c9cd56d3944e2723065d849b39 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 20 Jul 2026 10:39:30 +0700 Subject: [PATCH 3/7] replace a couple other factory functions with DI --- src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs | 6 ++---- src/SIL.LCModel/Infrastructure/Impl/CmObjectSurrogate.cs | 4 ++-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs index 81cc7400f..c105a96c2 100644 --- a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs +++ b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs @@ -102,12 +102,10 @@ public IServiceProvider CreateServiceLocator() (ICmObjectRepositoryInternal)sp.GetRequiredService()); // Add surrogate factory (internal); - services.AddSingleton(sp => - new CmObjectSurrogateFactory(sp.GetRequiredService())); + services.AddSingleton(); // Add surrogate repository (internal); - services.AddSingleton(sp => - new CmObjectSurrogateRepository(sp.GetRequiredService())); + services.AddSingleton(); // Add BEP. switch (m_backendProviderType) diff --git a/src/SIL.LCModel/Infrastructure/Impl/CmObjectSurrogate.cs b/src/SIL.LCModel/Infrastructure/Impl/CmObjectSurrogate.cs index e9a672888..8d1fefb69 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/CmObjectSurrogate.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/CmObjectSurrogate.cs @@ -755,7 +755,7 @@ internal sealed class CmObjectSurrogateRepository : ICmObjectSurrogateRepository /// Constructor /// /// - internal CmObjectSurrogateRepository(IdentityMap identityMap) + public CmObjectSurrogateRepository(IdentityMap identityMap) { if (identityMap == null) throw new ArgumentNullException("identityMap"); m_identityMap = identityMap; @@ -797,7 +797,7 @@ internal sealed class CmObjectSurrogateFactory : ICmObjectSurrogateFactory /// /// Constructor. /// - internal CmObjectSurrogateFactory(LcmCache cache) + public CmObjectSurrogateFactory(LcmCache cache) { if (cache == null) throw new ArgumentNullException("cache"); From 68190aff8dfbbcd3ce1aa5faf52674830c158df0 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Mon, 20 Jul 2026 11:40:55 +0700 Subject: [PATCH 4/7] set a default value for the transaction logger --- src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs b/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs index c3921a236..dbecfcaa4 100644 --- a/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs +++ b/src/SIL.LCModel/Infrastructure/Impl/UnitOfWorkService.cs @@ -153,7 +153,7 @@ internal BusinessTransactionState CurrentProcessingState { /// /// Constructor. /// - public UnitOfWorkService(IDataStorer dataStorer, IdentityMap identityMap, ICmObjectRepositoryInternal objectRepository, ILcmUI ui, ITransactionLogger logger) + public UnitOfWorkService(IDataStorer dataStorer, IdentityMap identityMap, ICmObjectRepositoryInternal objectRepository, ILcmUI ui, ITransactionLogger? logger = null) { if (dataStorer == null) throw new ArgumentNullException("dataStorer"); if (identityMap == null) throw new ArgumentNullException("identityMap"); From 4f9e2b051655ff03b16fed80398a8341ee4e0d10 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 22 Jul 2026 14:12:08 +0700 Subject: [PATCH 5/7] fix issue with get all instances --- src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs index c105a96c2..f485868b2 100644 --- a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs +++ b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs @@ -291,7 +291,8 @@ protected override object DoGetInstance(Type serviceType, string key) protected override IEnumerable DoGetAllInstances(Type serviceType) { var enumerableType = typeof(IEnumerable<>).MakeGenericType(serviceType); - var instances = (IEnumerable?)m_serviceProvider.GetServices(enumerableType); + //not using GetServices because it will throw an exception if the service is not found. + var instances = (IEnumerable?)m_serviceProvider.GetService(enumerableType); return instances ?? Enumerable.Empty(); } From 3a708003078effa92bf71f6622a36ab197479546 Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Wed, 22 Jul 2026 14:20:38 +0700 Subject: [PATCH 6/7] Add smoke tests for LCM service locator functionality - Introduced LcmServiceLocatorTests to verify core services resolution and instance retrieval. - Added tests for checking registered services and handling unregistered types. --- .../Infrastructure/LcmServiceLocatorTests.cs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs diff --git a/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs b/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs new file mode 100644 index 000000000..d6fe9f729 --- /dev/null +++ b/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2026 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using System.Linq; +using NUnit.Framework; +using SIL.LCModel.Core.KernelInterfaces; + +namespace SIL.LCModel.Infrastructure +{ + /// + /// Smoke tests for the LCM service locator wiring. + /// + [TestFixture] + public class LcmServiceLocatorTests : MemoryOnlyBackendProviderTestBase + { + /// + /// Verify that core services resolve and shortcut properties match GetInstance. + /// + [Test] + public void SmokeTest_ResolvesCoreServices() + { + var sl = Cache.ServiceLocator; + Assert.That(sl, Is.Not.Null); + + Assert.That(sl.GetInstance(), Is.Not.Null); + Assert.That(sl.GetInstance(), Is.Not.Null); + Assert.That(sl.GetInstance(), Is.Not.Null); + + Assert.That(sl.DataSetup, Is.SameAs(sl.GetInstance())); + Assert.That(sl.ObjectRepository, Is.SameAs(sl.GetInstance())); + Assert.That(sl.MetaDataCache, Is.SameAs(sl.GetInstance())); + Assert.That(sl.ActionHandler, Is.SameAs(sl.GetInstance())); + } + + /// + /// GetAllInstances returns registered services and an empty sequence for unknown types. + /// + [Test] + public void GetAllInstances_ReturnsRegisteredServices() + { + var instances = Cache.ServiceLocator.GetAllInstances().ToList(); + Assert.That(instances, Is.Not.Empty); + Assert.That(instances[0], Is.SameAs(Cache.ServiceLocator.GetInstance())); + } + + /// + /// GetAllInstances returns an empty sequence when the type is not registered. + /// + [Test] + public void GetAllInstances_ReturnsEmptyForUnregisteredType() + { + var instances = Cache.ServiceLocator.GetAllInstances().ToList(); + Assert.That(instances, Is.Empty); + } + + private interface IUnregisteredForServiceLocatorTest { } + } +} From 1c53bc57cbda7cae90f181b03aab24d37f8d491f Mon Sep 17 00:00:00 2001 From: Kevin Hahn Date: Fri, 24 Jul 2026 12:05:13 +0700 Subject: [PATCH 7/7] cleanup comments rename MediServiceLocator to MicrosoftServiceLocator --- src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs | 9 ++++----- .../Infrastructure/LcmServiceLocatorTests.cs | 13 ++++--------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs index f485868b2..1091e1210 100644 --- a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs +++ b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs @@ -126,7 +126,6 @@ public IServiceProvider CreateServiceLocator() services.AddTransient(sp => (IDataStorer)sp.GetRequiredService()); services.AddTransient(sp => (IDataReader)sp.GetRequiredService()); - // Add Mediator services.AddSingleton(); // Register additional interfaces for the UnitOfWorkService. services.AddTransient(sp => @@ -187,7 +186,7 @@ public IServiceProvider CreateServiceLocator() var serviceProvider = services.BuildServiceProvider(); - return new MediServiceLocator(serviceProvider); + return new MicrosoftServiceLocator(serviceProvider); } } @@ -197,7 +196,7 @@ public IServiceProvider CreateServiceLocator() /// so downstream code that binds GetInstance<T> /// to the interface method keeps working, source- and binary-compatible. /// - internal sealed class MediServiceLocator : ServiceLocatorImplBase, + internal sealed class MicrosoftServiceLocator : ServiceLocatorImplBase, ILcmServiceLocator, IServiceLocatorInternal, IDisposable { private ServiceProvider m_serviceProvider; @@ -205,7 +204,7 @@ internal sealed class MediServiceLocator : ServiceLocatorImplBase, /// /// Constructor /// - internal MediServiceLocator(ServiceProvider serviceProvider) + internal MicrosoftServiceLocator(ServiceProvider serviceProvider) { m_serviceProvider = serviceProvider; } @@ -213,7 +212,7 @@ internal MediServiceLocator(ServiceProvider serviceProvider) #region Disposable stuff #if DEBUG /// - ~MediServiceLocator() + ~MicrosoftServiceLocator() { Dispose(false); } diff --git a/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs b/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs index d6fe9f729..2f5994415 100644 --- a/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs +++ b/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs @@ -14,9 +14,7 @@ namespace SIL.LCModel.Infrastructure [TestFixture] public class LcmServiceLocatorTests : MemoryOnlyBackendProviderTestBase { - /// - /// Verify that core services resolve and shortcut properties match GetInstance. - /// + /// [Test] public void SmokeTest_ResolvesCoreServices() { @@ -33,9 +31,7 @@ public void SmokeTest_ResolvesCoreServices() Assert.That(sl.ActionHandler, Is.SameAs(sl.GetInstance())); } - /// - /// GetAllInstances returns registered services and an empty sequence for unknown types. - /// + /// [Test] public void GetAllInstances_ReturnsRegisteredServices() { @@ -44,9 +40,8 @@ public void GetAllInstances_ReturnsRegisteredServices() Assert.That(instances[0], Is.SameAs(Cache.ServiceLocator.GetInstance())); } - /// - /// GetAllInstances returns an empty sequence when the type is not registered. - /// + + /// [Test] public void GetAllInstances_ReturnsEmptyForUnregisteredType() {