Skip to content

Commit

Permalink
Address issue #578 by providing an internal service provider we fall …
Browse files Browse the repository at this point in the history
…back on when no services have been configured (either explicitly via XbimServices.ConfigureServices, or implicitly in static initialisers in IfcStore, GeometryEngine etc).

Updated Readme
  • Loading branch information
andyward committed Sep 18, 2024
1 parent d1b9ce5 commit 2ad4419
Showing 5 changed files with 133 additions and 46 deletions.
65 changes: 29 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -20,10 +20,9 @@ _[**B**uilding **I**nformation **M**odelling](https://en.wikipedia.org/wiki/Buil
the .NET platform. This library enables software developers to easily read, write, validate and interrogate data in
the buildingSmart [IFC formats](https://en.wikipedia.org/wiki/Industry_Foundation_Classes), using any .NET language.

As of version 6.0 XbimEssentials includes support for `.netstandard2.0`, `.netstandard2.1` and `.net6.0`. Supporting `netstandard2.0` provides support .NET Framework 4.7.2 upwards.
Earlier .NET Framewaork versions may work, but we can't provide any support for them.


As of version 6.0 XbimEssentials includes support for `.netstandard2.0`, `.netstandard2.1`, `.net6.0` and `net8.0`.
Supporting `netstandard2.0` provides support .NET Framework 4.7.2 upwards.
Earlier .NET Framework versions may work, but we can't provide any support for them.

## Background / Motivation ##

@@ -35,7 +34,9 @@ This library supports *STEP*, *IfcXml* and *IfcZip* formats, and enables you to
[Ifc4 Addendum 2](http://www.buildingsmart-tech.org/specifications/ifc-releases/ifc4-add2)).

The wider XBIM toolkit contains additional repositories with libraries to read and write related Open BIM formats including
[COBie](https://github.com/xBimTeam/XbimExchange) and BIM Collaboration Format ([BCF](https://github.com/xBimTeam/XbimBCF))
[COBie](https://github.com/xBimTeam/XbimCobieExpress) and BIM Collaboration Format ([BCF](https://github.com/xBimTeam/XbimBCF))

xbim Toolkit also supports IFC model verification with IDS using our [IDS Library](https://github.com/xBimTeam/Xbim.IDS.Validator).

In order to visualise 3D Geometries you will need to include the [Xbim.Geometry](https://github.com/xBimTeam/XbimGeometry)
package which provides full support for geometric, topological operations and visualisation.
@@ -50,14 +51,20 @@ of STEP/Express parsing, 3D graphics - enabling you to work at a higher level th
Please see our [ChangeLog](CHANGELOG.md) for details on what's new and what you need to upgrade.
In particular, please **note the following section copied here:**

> **BREAKING CHANGE**: V6 implements a new mechanism for discovering internal resources and uses standard (.net Dependency Injection patterns)[https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection] for managing services internally.
> `Xbim.Common` now introduces an internal DI service that you can optionally integrate with your own DI implementation, for providing Logging services, and configuring xbim service behaviour. E.g. Invoking:
> ` XbimServices.Current.ConfigureServices(s => s.AddXbimToolkit(opt => opt.UseMemoryModel().UseLoggerFactory(yourloggerFactory)));
> registers the Toolkit internal dependencies, uses an In-memory model and inserts your configured ILoggerFactory into the service in place of our default one.
> **BREAKING CHANGE**: xbim Toolkit V6 implements a new mechanism for registering internal resources and makes use of standard [.net Dependency Injection patterns](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection) for managing services internally.
> `Xbim.Common` now introduces an internal DI service that you can optionally integrate with your own DI implementation,
> for providing Logging services, and configuring xbim service behaviour such as the model provider to use. E.g. Invoking:
>
> `XbimServices.Current.ConfigureServices(s => s.AddXbimToolkit(opt => opt.UseMemoryModel().UseLoggerFactory(yourloggerFactory)));`
>
> registers the Toolkit internal dependencies, configures an In-memory model-provider and adds an existing ILoggerFactory into the service in place of our default one.
> `IfcStore.ModelProviderFactory` has been deprecated as this is provided for by the above mechanism
> The persistent EsentModel is now available automatically through IfcStore (on Windows) so there is no need for the `UseHeuristicModelProvider()` 'ceremony' to ensure Esent is used.
> Note: The default Logging implementation has been removed from Toolkit to reduce the amount of dependencies. To enable is a simple matter of adding logging to the Xbim Services.
> See (Example)[https://github.com/xBimTeam/XbimEssentials/blob/b3fce6f40a31fb87a75b24888a4e20740a378e31/Tests/DependencyInjectionTests.cs#L96]
> The persistent **EsentModel** is now available automatically through IfcStore (on Windows) so there is no need for the `UseHeuristicModelProvider()` 'ceremony'
> to ensure Esent is used although you can specify it explicitly with `XbimServices.Current.ConfigureServices(s => s.AddXbimToolkit(opt => opt.UseHeuristicModel())`
>
> Note: The default Logging implementation has been removed from Toolkit to reduce the amount of external dependencies.
> To enable is a simple matter of adding your preferred logging implementation to the Xbim Services.
> See [Example](https://github.com/xBimTeam/XbimEssentials/blob/b3fce6f40a31fb87a75b24888a4e20740a378e31/Tests/DependencyInjectionTests.cs#L96)

## Code Examples
@@ -228,11 +235,11 @@ and an ability open sementic model data from a JSON structure.

## Getting Started

You will need Visual Studio 2015 or newer to compile the Solution. Visual Studio 2017 is recommended.
Prior versions of Visual Studio should work, but we'd recomments 2017 where possible
The [free VS 2017 Community Edition](https://visualstudio.microsoft.com/downloads/) should work fine.
All projects target .NET Framework 4.7, with some projects also targeting .netstandard2.0, which should
permit limited trials of XBIM with .NET Core / Mono etc.
You will need Visual Studio 2019 or newer to compile the Solution. Visual Studio 2022 is recommended.
Prior versions of Visual Studio should work.
The [free VS 2022 Community Edition](https://visualstudio.microsoft.com/downloads/) should work fine.
All projects target .NET Framework 4.7, with some projects also targeting .netstandard2.0, which
supports modern .NET Core implementations.

### Using the library

@@ -250,7 +257,6 @@ dependent packages directly. (Which is necessary for .NET Core currently, as Ess

## Toolkit Overview

![XBIM Libraries - high level dependencies](docs/img/XBIM-Architecture-0_1.png)

### How to use it?

@@ -280,15 +286,11 @@ the licence agreements.

The core XBIM library makes use of the following 3rd party software packages, under their associated licences:

* 'OpenCASCADE' Geometry Engine : http://www.opencascade.org/ - OPEN CASCADE Public License
* 'Gardens Point Parser Generator' http://gppg.codeplex.com/ - New BSD Licence
* Elements of '3D Tools' WPF library http://3dtools.codeplex.com/ - MS Permissive Licence
* Log4net : http://logging.apache.org/log4net/ - Apache 2.0 Licence
* NPOI : http://npoi.codeplex.com - Apache 2.0 Licence
* NewtonSoft JSON : http://json.codeplex.com/ - MIT Licence
* 'Gardens Point Parser Generator' https://github.com/KommuSoft/Gardens-Point-Parser-Generator - New BSD Licence
* 'OpenCASCADE' Geometry Engine : http://www.opencascade.org/ - GNU Lesser General Public License (LGPL) version 2.1, with additional [exception](https://dev.opencascade.org/doc/overview/html/occt_public_license.html#occt_lgpl_exception)

All 3rd party licences are permissive-style licences. We actively avoid Copyleft/GPL style licences to retain
compatibility with our CDDL licence - meaning you can use the XBIM Toolkit in a closed-source commercial software package.
All 3rd party licences are permissive-style licences. We actively avoid Strong Copyleft/GPL style licences to retain
compatibility with our CDDL licence - meaning you can use the XBIM Toolkit in a closed-source commercial software package.

## Support & Help

@@ -301,16 +303,7 @@ For bugs, and improvements, please use the GitHub Issues of the relevant reposit
If you have a question, or need some help, you may find the
[Stackoverflow xbim](https://stackoverflow.com/questions/tagged/xbim) tag a good place to start.

## Acknowledgements
While we do not qualify anymore for open source licenses of JetBrains, we would like to acknowledge the
good work and thank [JetBrains](https://www.jetbrains.com/) for supporting the XbimToolkit project with
free open source [Resharper](https://www.jetbrains.com/resharper/) licenses in the past.

[![ReSharper Logo](https://raw.githubusercontent.com/xBimTeam/XbimWindowsUI/master/ReadmeResources/icon_ReSharper.png)](https://www.jetbrains.com/resharper/)

Thanks also to Microsoft Azure DevOps for the use of [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/)
to automate our builds.

## Getting Involved

If you'd like to get involved and contribute to this project, please read the [CONTRIBUTING ](https://github.com/xBimTeam/XbimEssentials/blob/master/CONTRIBUTING.md) page or contact the Project Coordinators @CBenghi and @martin1cerny.
If you'd like to get involved and contribute to this project, please read the [CONTRIBUTING](https://github.com/xBimTeam/XbimEssentials/blob/master/CONTRIBUTING.md) page or contact the Project Coordinators @CBenghi and @martin1cerny.
22 changes: 21 additions & 1 deletion Tests/DependencyInjectionTests.cs
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
using System.IO;
using Xbim.Common;
using Xbim.Common.Configuration;
using Xbim.Common.Model;
using Xbim.Common.Step21;
using Xbim.Ifc;
using Xbim.IO;
@@ -21,7 +22,7 @@ public class DependencyInjectionTests
public DependencyInjectionTests()
{
// Clear the singleton collection each test
SuT = XbimServices.CreateInstance();
SuT = XbimServices.CreateInstanceInternal();
}

private XbimServices SuT;
@@ -250,6 +251,25 @@ public void Services_Are_Registered_Once_Only()

}

[Fact()]
public void Minimial_Fallback_Services_Are_Always_Available()
{

XbimServices.Current.Rebuild();
using (FileStream fs1 = new FileStream(@"TestFiles/4walls1floorSite.ifc", FileMode.Open))
{
var header = StepModel.LoadStep21Header(fs1);

header.Should().NotBeNull();

}
// Add explicitly. Normally set up by IfcStore static ctor etc, but that isundone by XbimServices.Current.Rebuild()
XbimServices.Current.ConfigureServices(s => s.AddXbimToolkit(t => t.AddHeuristicModel()));
using FileStream fs2 = new FileStream(@"TestFiles/4walls1floorSite.ifc", FileMode.Open);
var ifcStore = IfcStore.Open(fs2, StorageType.Ifc, XbimModelType.MemoryModel);
ifcStore.Instances.Should().NotBeEmpty();
}


private class DummyModelProvider : IModelProvider
{
59 changes: 59 additions & 0 deletions Xbim.Common/Configuration/InternalServiceProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Diagnostics;

namespace Xbim.Common.Configuration
{
/// <summary>
/// An Internal Service provider which serves as a fall back to a minimal services implementation when the consuming application
/// does not yet explicitly registered the xbim services or <see cref="IServiceProvider"/> on <see cref="XbimServices"/>.
/// The fallback services provides a safe baseline but provides no Logging capability.
/// XBIM INTERNAL USE ONLY. Prefer <see cref="XbimServices"/>
/// </summary>
internal class InternalServiceProvider
{
/// <summary>
/// Gets the internal ServiceProvider
/// </summary>
public IServiceProvider ServiceProvider { get; }

private readonly static Lazy<InternalServiceProvider> lazySingleton;

static InternalServiceProvider()
{
lazySingleton = new Lazy<InternalServiceProvider>(() => new InternalServiceProvider());
}

private InternalServiceProvider()
{
// Set up minimal services required to use Geometry.
var services = new ServiceCollection();
// Provide a NullLogger implementation so DI dependencies are satsisfied. We don't have a concrete Logger implementation we can use
services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(NullLogger<>)));

//services.AddXbimToolkit(opt => opt);
ServiceProvider = services.BuildServiceProvider();

var warning = @$"NOTE: The xbim InternalServices are being used because Xbim.Common.Configuration.XbimServices has not yet been configured. This fallback service provider has no logging support so you may miss useful output from xbim. To see xbim logs ensure you provide a LoggerFactory to {typeof(XbimServices).FullName} at startup - or provide an existing ServiceProvider to the XbimServices. e.g.
XbimServices.Current.ConfigureServices(s => s.AddXbimToolkit(c => c.AddLoggerFactory(loggerFactory)));
// or
XbimServices.Current.UseExternalServiceProvider(serviceProvider);";

Debug.WriteLine(warning);
Console.Error.WriteLine(warning);
}

/// <summary>
/// Gets the Current instance of the <see cref="InternalServiceProvider"/>
/// </summary>
public static InternalServiceProvider Current { get => lazySingleton.Value; }



}
}
31 changes: 23 additions & 8 deletions Xbim.Common/Configuration/XbimServices.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Linq;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Xbim.Essentials.Tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010029a3c6da60efcb3ebe48c3ce14a169b5fa08ffbf5f276392ffb2006a9a2d596f5929cf0e68568d14ac7cbe334440ca0b182be7fa6896d2a73036f24bca081b2427a8dec5689a97f3d62547acd5d471ee9f379540f338bbb0ae6a165b44b1ae34405624baa4388404bce6d3e30de128cec379147af363ce9c5845f4f92d405ed0")]
@@ -8,8 +9,12 @@
namespace Xbim.Common.Configuration
{
/// <summary>
/// Class encapsulating the services used in the application managed by Dependency Injection
/// Class encapsulating the services and <see cref="IServiceProvider"/>s used in the application.
/// Consumers can provide their own built service provider, or configure the internal xbim <see cref="IServiceProvider"/>.
/// </summary>
/// <remarks>the service delays building of the internal service provider when not configured to support
/// late configutation of the services.
/// In this case the service provision is delagated to <see cref="InternalServiceProvider"/></remarks>
public class XbimServices
{

@@ -19,15 +24,16 @@ private XbimServices()
}

/// <summary>
/// For testing only
/// Create a new XbimServiecs instance. For testing only
/// </summary>
/// <returns></returns>
internal static XbimServices CreateInstance()
/// <returns>a private instance of the <see cref="XbimServices"/></returns>
public static XbimServices CreateInstanceInternal()
{
return new XbimServices();
}

private bool isBuilt = false;


private IServiceCollection servicesCollection = new ServiceCollection();
private IServiceProvider externalServiceProvider = null;
@@ -98,6 +104,12 @@ public void UseExternalServiceProvider(IServiceProvider provider)
/// </summary>
public bool IsBuilt { get => isBuilt; }

/// <summary>
/// Flag indicating the DI container has been configured. When not configured the ServiceProvider will delay being built, and
/// service provision will fall back to an internal <see cref="IServiceProvider"/>.
/// </summary>
public bool IsConfigured { get => servicesCollection.Any() || externalServiceProvider != null; }

/// <summary>
/// Configure the internal <see cref="IServiceCollection"/>.
/// </summary>
@@ -116,23 +128,26 @@ public void ConfigureServices(Action<IServiceCollection> configure)
/// <summary>
/// Gets a <see cref="IServiceProvider"/> used for resolving xbim services
/// </summary>
/// <remarks>Used when an external DI service is not employed</remarks>
public IServiceProvider ServiceProvider => externalServiceProvider ?? serviceProviderBuilder.Value;
/// <remarks>To avoid building the service provider too early, when not using an external service provider,
/// and the xbim service has not been configured, the system reverts to a basic internal <see cref="IServiceProvider"/></remarks>
public IServiceProvider ServiceProvider => externalServiceProvider ??
(IsConfigured ? serviceProviderBuilder.Value : InternalServiceProvider.Current.ServiceProvider);

/// <summary>
/// For internal and unit test purposes only
/// Rebuilds the internal xbim DI / Service Provider. For internal and unit test purposes only
/// </summary>
internal void Rebuild()
{
servicesCollection.Clear();
isBuilt = false;

if (serviceProviderBuilder != null &&
serviceProviderBuilder.IsValueCreated &&
serviceProviderBuilder.Value is ServiceProvider sp)
{
sp.Dispose();
}
//rebuild the Lazy so IServiceProvider is rebuild next time
//rebuild the Lazy so IServiceProvider is rebuilt next time
serviceProviderBuilder = new Lazy<IServiceProvider>(() =>
{
isBuilt = true;
2 changes: 1 addition & 1 deletion Xbim.Essentials.NetCore.Tests/DependencyInjectionTests.cs
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ public class DependencyInjectionTests
public void ServiceProviderIsValid()
{

var SuT = XbimServices.CreateInstance();
var SuT = XbimServices.CreateInstanceInternal();
SuT.ConfigureServices(s =>
{
var services = s.AddXbimToolkit(opt => opt.AddEsentModel())

0 comments on commit 2ad4419

Please sign in to comment.