diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a4b2c64..e4703fbcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,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] Trim 12 overwordy semantic domain descriptions and fix 22 punctuation/whitespace issues in the SemDom.xml template, matching sillsdev/FwLocalizations#5 and sillsdev/FwLocalizations#7 - [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 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/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..1091e1210 100644 --- a/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs +++ b/src/SIL.LCModel/IOC/LcmServiceLocatorFactory.cs @@ -6,8 +6,10 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using CommonServiceLocator; +using Microsoft.Extensions.DependencyInjection; using SIL.LCModel.Application; using SIL.LCModel.Application.Impl; using SIL.LCModel.Core.KernelInterfaces; @@ -17,9 +19,6 @@ using SIL.LCModel.DomainServices.DataMigration; using SIL.LCModel.Infrastructure; using SIL.LCModel.Infrastructure.Impl; -using SIL.Reporting; -using StructureMap; -using StructureMap.Pipeline; namespace SIL.LCModel.IOC { @@ -65,77 +64,48 @@ 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(); // 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(); // 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(); // Add surrogate repository (internal); - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(); // Add BEP. switch (m_backendProviderType) @@ -143,147 +113,106 @@ public IServiceProvider CreateServiceLocator() default: throw new InvalidOperationException(Strings.ksInvalidBackendProviderType); case BackendProviderType.kXML: - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(); break; case BackendProviderType.kMemoryOnly: - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(); break; case BackendProviderType.kSharedXML: - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(); 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()); - - // Add Mediator - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddTransient(sp => (IDataStorer)sp.GetRequiredService()); + services.AddTransient(sp => (IDataReader)sp.GetRequiredService()); + + services.AddSingleton(); // 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(); // Add ReferenceAdjusterService - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(sp => new ReferenceAdjusterService()); // Add SDA - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + services.AddSingleton(); // Add loader helper - registry - .For() - .LifecycleIs(new SingletonLifecycle()) - .Use(); + 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(); // 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 MicrosoftServiceLocator(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 MicrosoftServiceLocator : ServiceLocatorImplBase, ILcmServiceLocator, IServiceLocatorInternal, IDisposable { - private Container m_container; + private ServiceProvider m_serviceProvider; /// /// Constructor /// - internal StructureMapServiceLocator(Container container) + internal MicrosoftServiceLocator(ServiceProvider serviceProvider) { - m_container = container; + m_serviceProvider = serviceProvider; } #region Disposable stuff #if DEBUG /// - ~StructureMapServiceLocator() + ~MicrosoftServiceLocator() { Dispose(false); } @@ -314,21 +243,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 +267,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,10 +289,10 @@ protected override object DoGetInstance(Type serviceType, string key) /// protected override IEnumerable DoGetAllInstances(Type serviceType) { - foreach (object obj in m_container.GetAllInstances(serviceType)) - { - yield return obj; - } + var enumerableType = typeof(IEnumerable<>).MakeGenericType(serviceType); + //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(); } #endregion diff --git a/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs b/src/SIL.LCModel/Infrastructure/Impl/CmObjectIdentityMap.cs index 79b794b3c..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"); @@ -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/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"); 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..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. /// - internal 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"); 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) { 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 fcb5e0995..96f0749bc 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; diff --git a/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs b/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs new file mode 100644 index 000000000..2f5994415 --- /dev/null +++ b/tests/SIL.LCModel.Tests/Infrastructure/LcmServiceLocatorTests.cs @@ -0,0 +1,54 @@ +// 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 + { + /// + [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())); + } + + /// + [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())); + } + + + /// + [Test] + public void GetAllInstances_ReturnsEmptyForUnregisteredType() + { + var instances = Cache.ServiceLocator.GetAllInstances().ToList(); + Assert.That(instances, Is.Empty); + } + + private interface IUnregisteredForServiceLocatorTest { } + } +}