xBimTeam / XbimEssentials

A .NET library to work with data in the IFC format. This is the core component of the Xbim Toolkit
https://xbimteam.github.io/
Other
494 stars 173 forks source link

StepModel.LoadStep21Header() and IfcStore.Open() interaction #578

Open dm-dd opened 1 month ago

dm-dd commented 1 month ago

Hello, I found an issue when calling the StepModel.LoadStep21Header() method before the IfcStore.Open() method, the version affected is V5.1.274.

The following code throws the exception "System.InvalidOperationException: 'No service for type 'Xbim.IO.IModelProvider' has been registered.'" and is not correlated to which file is being read.

FileStream fs1 = new FileStream(@"anyfile1.ifc", FileMode.Open);
var header1 = StepModel.LoadStep21Header(fs1);

FileStream fs2 = new FileStream(@"anyfile2.ifc", FileMode.Open);
var ifcStore = IfcStore.Open(fs2, StorageType.Ifc, XbimModelType.MemoryModel);

As a workaround I found that is sufficient to add this at the start of the code:

if (!XbimServices.Current.IsBuilt)
{
    if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
    {
        XbimServices.Current.ConfigureServices(s => s.AddXbimToolkit(opt => opt.AddHeuristicModel()));
    }
    else
    {
        XbimServices.Current.ConfigureServices(s => s.AddXbimToolkit(opt => opt.AddMemoryModel()));
    }
}

and the exception is prevented.

Is this actually an issue or was I doing something wrong by using only the code in the first snippet?

andyward commented 1 month ago

That's a reasonable workaround - and basically what we recommend in the readme / changelog.

This issue is that we have an internal IServiceProvider we try to set up for both internal configuration and 3rd party extensibility/configuration. This service provider is only designed to be built once, unfortunately what is happening is that calling StepModel.LoadStep21Header() triggers the provider in XbimServices to be constructed before IfcStore has had a chance to register things in its static initialiser.

I'm not altogether happy with the pattern we have (especially static initialisers, and the use of serviceLocator anti-pattern) but that's a bit forced on us due to some of the interfaces not being DI friendly without major breaking changes to the interface.

My thoughts are we should provide a fallback serviceProvider we use when the environment may not have been configured fully yet.