specflowoss / bodi Goto Github PK
View Code? Open in Web Editor NEWA very small, embeddable IoC container (for SpecFlow)
License: Apache License 2.0
A very small, embeddable IoC container (for SpecFlow)
License: Apache License 2.0
As noted in an ignored integration test: BoDi.Tests.RegisterFactoryDelegateTests.ShouldThrowExceptionForDynamicCircuarDepenencies - if a factory delegate registers a component and makes use of a resolved IObjectContainer
inside of the registration, circular dependencies are not detected if they exist in the resolved objects.
Here's the example from that integration test:
container.RegisterFactoryAs<ClassWithCircularDependency1>(c => new ClassWithCircularDependency1(c.Resolve<ClassWithCircularDependency2>()));
container.Resolve<ClassWithCircularDependency1>();
Here, we have two 'resolutions' occurring. An outer resolution - in which the container resolves a factory registration for ClassWithCircularDependency1
and an inner resolution in which the container resolves an unregistered type for ClassWithCircularDependency2
.
The root cause of this is that when resolving an IObjectContainer
and using this to dynamically resolve dependencies, that inner resolve operation does not have access to the "resolution path" information of the outer resolution operation. This thwarts circular dependency detection because the resolution path chain is repeatedly broken and started again from fresh, and never comes full circle.
The resolution path looks like:
I have a solution for this - which is that when resolving an IObjectContainer
and the object container is not the very first thing in the resolution path, then instead of resolving and returning the real object container, instead return an instance of a proxy class which also implements the same interface (and contains the real container as a private field). That same proxy type would also receive the "resolution path" information from the parent/outer resolution operation.
Within the proxy type, most calls may just be proxied wholesale, but calls to resolve things would instead use a resolve method which passes on that resolution path information. Thus the continuity of the 'overall' resolve operation (the outer and the inner) is preserved. Thus there is only ever one resolution path in play and it looks like so:
At this stage, circular dependency detection may trigger and raise an exception, because ClassWithCircularDependency1
appears twice in the path.
As a project of my own, I have forked BoDi. I have fixed this very same problem in that project but it's diverged so much that it would be impossible to do a simple back-port of the fix into BoDI. Indeed, I've since archived that forked repo in order to create a new non-forked one.
However, you can find my fix in this this issue in that archived repo. The relevant commits should all be findable from the issue.
In the class FactoryRegistration, Resolve method, there is a TODO comment indicating that the created instance should perhaps be added to the pool of created instances.
This would mean that the created instance is cached in the container (future resolutions using the same key would return the same instance), and the created instance could also be disposed along with the container.
I believe that this is indeed a desirable feature and it should be implemented.
In order to preserve backwards compatibility with the current behaviour, the method RegisterFactoryAs
could be extended with a new overload which accepts a boolean indicating whether the object should be pooled, with a default value of false
.
I am open to discussion on whether it is important or not to support the current behaviour (each resolve call gets a new instance, and instances are not disposed by the container) or whether that behaviour should simply be considered a 'bug'. Does anybody rely on it?
I'd like to be able to update a registered instance after it has been resolved.
Currently, when I try to do this, I get the exception:
ObjectContainerException: "An object has been resolved for this interface already.
For reference, AutoFac
has this ability with an Update
method.
Thank you.
I'm using a SpecFlow runtime plugin and the RegisterGlobalDependencies event to add my own dependencies to the global SpecFlow container.
When I ask a sub-container (Feature- or ScenarioContainer) inside the test execution whether the dependency is registered through the IsRegistered call it returns always false. This is because IsRegistered only looks at the registrations of the current container, not the base container. Now the weird thing is that you can successfully call Resolve with the same interface and get the dependency. This is because Resolve will ask the base container for resolving the type if it cannot resolve the type itself whereas IsRegistered will not consult the base container.
This should be fixed by making IsRegistered also check the base container registrations.
Hi,
I know that IDisposable
instances registered by RegisterInstanceAs
can be disposed by BoDI after each scenario only if dispose
parameter is set to true
. I want my instances to be created by factory method so I use RegisterFactoryAs
registration method. I noticed that those instances are not stored in objectPool
and they are not subjects for disposing in container's Dispose
method.
Can you confirm that my observations are true? I'm using SpecFlow 3.3.30 and BoDI 1.4.1.
Thanks,
Was just wondering... I noticed that in latest master code (https://github.com/gasparnagy/BoDi/blob/master/BoDi/BoDi.cs), a new feature has been added to allow a choice of strategy for type and factory registrations. This is exactly what I have been looking to do in a project I am using. Are there any imminent plans to release a new version with this feature and update Specflow to depend upon it?
Cheers,
Steve
There is a scenario which requires the following functionality to be available:
public void RegisterCollection(Type dependencyType, IEnumerable<Type> dependencyTypes)
This should register the collection of types as a collection of their base type, i.e.
if dependencyType
is ICommonSomething
and dependencyTypes
contains two types FirstImplementation
and SecondImplementation
then upon constructor injection of IEnumerable<ICommonSomething>
this parameter will be resolved into collection of FirstImplementation
and SecondImplementation
.
This is not a bug, merely a feature request.
The BoDi.NuGetPackages
project has only a single configuration: Debug
(and this is not declared in the usual .NET way).
The parent .sln file specifies that this project maps to a number of configurations:
When opening the solution in Monodevelop, it complains about this as an invalid mapping of configurations. Propose switching the single configuration for this project to Release
and then mapping all solution build configurations to that release config.
Rationale for picking release: It's a nuget packaging project, it's only ever going to matter for a release build anyway.
BoDi looks like a great project, but I'm not sure I agree with the design guideline that everything is contained within a single source file.
With NuGet being so prevalent (and BoDi having NuGet packages available) it's very easy to include the container as a NuGet package. However, for the purpose of maintainability, having all of the source in a single file is somewhat inconvenient.
I appreciate that for SpecFlow it's going to be desirable to do that because - I presume - BoDi isn't included as a 3rd party dependency but rather the whole source file is just copy-pasted into SpecFlow's source. Given the extremely close relationships between the projects this is reasonable for SpecFlow.
For other projects which might want to reuse BoDi, that can be less optimal because (for example) upgrading BoDi is impossible without a fresh build of the dependent project.
I'm open to discussion on this. My rationale is quite simply that I'd like to reuse BoDi for my own testing-related project (which needs a small DI container). I think I also would like to make a few enhancements, but if I make more than just trivial changes then I'm going to want to bust it up into single-file-per-class.
Hi @gasparnagy !
I wonder if BoDi is thread-safe.
From source code it seems that BoDi is not thread safe and not support multiple parallel Resolve(..)
.
But Specflow is passing BoDi containers pretty freely. It's up to caller to prevent reentrancy?
If yes, let's document it
I am writing a plugin for SpecFlow to support Ninject. Somewhere in my tests, it turns I resolve the IObjectContainer
out of my Ninject kernel and then I dispose the IObjectContainer. To be more precise, I am doing something like this:
[Test]
public void ObjectContainer_Can_Be_Resolved_From_Scenario_Kernel()
{
// Arrange
var plugin = new NinjectPlugin();
this.globalContainer.RegisterTypeAs<NinjectTestObjectResolver, ITestObjectResolver>();
this.globalContainer
.RegisterTypeAs<TScenarioContainerFinder, ContainerFinder<ScenarioDependenciesAttribute>>();
this.globalContainer
.RegisterTypeAs<TFeatureContainerFinder, ContainerFinder<FeatureDependenciesAttribute>>();
this.globalContainer
.RegisterTypeAs<TTestThreadContainerFinder, ContainerFinder<TestThreadDependenciesAttribute>>();
plugin.Initialize(events, Mock.Of<RuntimePluginParameters>(), Mock.Of<UnitTestProviderConfiguration>());
events.RaiseCustomizeGlobalDependencies(this.globalContainer, this.specFlowConfiguration);
using var expectedScenarioContainer = new ObjectContainer(this.globalContainer);
expectedScenarioContainer .RegisterInstanceAs(
new ScenarioInfo(string.Empty, string.Empty, Array.Empty<string>(), new OrderedDictionary()));
this.pluginEvents.RaiseCustomizeScenarioDependencies(scenarioContainer);
var scenarioKernel = expectedScenarioContainer.Resolve<IKernel>();
// Act
var actualScenarioContainer = scenarioKernel.Get<IObjectContainer>();
// Assert
actualScenarioContainer.Should().BeSameAs(expectedScenarioContainer);
}
At the end of that test, expectedScenarioContainer.Dispose()
is called, thanks to the using
directive. That calls the Dispose()
on the IObjectContainer
, which calls the Dispose()
method of my IKernel
(my ninject container). Because my IKernel
is provided with
kernel.Bind<IObjectContainer>().ToConstant(objectContainer);
the IObjectContainer.Dispose()
is once again called, therefore we hit this code concurrently. In that process, the ObjectContainer.objectPool.Values
are modified by another process within the loop, and the disposing process results in failure.
Would that be imagineable to fix that?
I'd like to request the ability to check if a type/instance/interface is registered or can be resolved.
As a work-around, I have to wrap a call to Resolve
in a try...catch (BoDi.ObjectContainerException)
.
In my scenario, I have two test projects, each have a [Before]
SpecFlow binding and register a different instance of an object of the same interface. For example, test project B references test project A and the test assembly generated by test project A is configured as an external step assembly in test project B's configuration file's stepAssemblies
. When running a scenario in test project B, test project B's [Before]
hook is run first and instance X is registered without issues. However, soon after that, test project A's [Before]
hook is run and overwrites the registered instance with its own instance Y. This causes an issue in the steps in test project B which expect the instance of that type to contain specific property values, etc.
So I'm thinking we may want to have an IsRegistered
and/or TryResolve
method.
Other options I've explored but don't seem to be practical solutions:
[Before]
method hook (i.e. dependency injection on the method).System.Reflection.TargetParameterCountException: Parameter count mismatch.
[Binding]
class.BoDi.ObjectContainerException: Interface cannot be resolved: IMyInterface
In both options, if the instance is null then it would indicate that the type/instance/interface could not be resolved.
Thank you.
Indexed source information could not be retrieved from 'path\BoDi.pdb'. Symbol indexes could not be retrieved.
Wondering is the debugger type set to full ? DebuggerType on .net 4.5 needs to be set to "full".
I'm using the latest version of SpecFlow (which in turn has the latest version of BoDi =. 1.4.1).
I would like to utulise the 'RegisterTypeAs' way of registering but the problem is the type I am registering has a ctor with parameters.
e.g. I have the following sample
_container.RegisterTypeAs<WebDriver, ICustomWebDriver>();
But the type WebDriver has a ctor that needs parameters.
The use case of doing it this way is so if I register multiple custom drivers they only get resolved (via constructor) for the binding classes that use them for a particular test. (i.e. not all tests need both drivers but some may).
Essentially something similar to Autofac (taken from their site):
// Using a NAMED parameter:
builder.RegisterType()
.As()
.WithParameter("configSectionName", "sectionName");
// Using a TYPED parameter:
builder.RegisterType()
.As()
.WithParameter(new TypedParameter(typeof(string), "sectionName"));
Hello,
Correct me if I'm wrong, but I understand that keeping all code in a single file is meant to ease embedding BoDi as a source file in a consuming project.
As this hurt readability of PRs (I hit that for #24), did you consider publishing a source nuget to allow embedding BoDi as source code for people not wanting to rely on the binary form ?
Example of such source code package can be found here: https://www.nuget.org/packages/Microsoft.Azure.WebJobs.Sources/
I'm not aware of any good reason to do this except to share code that is meant to be private a a repo while still needing to be share, which seems not the case here, but in case this would help to provide more readable contributions...
Regards,
Are there any plans to support IAsyncDisposable in this container?
It may be that BoDi has already adopted semantic versioning, it is somewhat difficult to tell because there is a NuGet release: 1.3.0 which is very difficult to pin down to a specific commit.
Going by the NuGet timestamp for that release (27th June 2017), it could be based on commit 599d7fef80b5049e9009360520b821223bca3887
or anything earlier; there's no specific activity which corresponds directly to that release date. More importantly, there is no git tag for 1.3.0 so it's hard to tell. To be more reusable for other open source projects, I would recommend adopting a policy of ensuring that all NuGet releases are tagged appropriately.
I want to update container with another container. Is it possible?
This is a regression introduced in v1.5.0
Execute specs found at
https://github.com/godrose/Solid/tree/v2.3.0/Solid.IoC.Adapters.BoDi.Specs
They should work.
Then upgrade the BoDi version
Run again. Observe the failing test.
I wish for the nuget packages to have the licence expression property set correctly and the repository url
The licence expression property should be set to the correct licence type (depending on the repo) as this will enable analysis of licences in use to occur in external tools & the license type will be shown in Nuget etc. By providing the repo url it will make it easier to contribute and can be used by source-link.
Repro steps:
Create a .NETStandard project
Create a .NETFramework project with BoDi container in it
Dynamically register the types defined in the .NETStandard project
Expected:
Type registration works
Actual: Exception is thrown: {"type mapping is not valid"} in BoDi.ObjectContainer.RegisterTypeAs(Type implementationType, Type interfaceType)
#16 by @MKMZ is breaking change.
In 7c0135c
Changing void RegisterTypeAs(..)
to IStrategyRegistration RegisterTypeAs(..)
is breaking change
https://stackoverflow.com/questions/9178533/why-adding-a-return-type-to-a-void-returning-method-causes-a-missingmethodexcept
If this is expected, this should be documented and major version bumped. If not, is should be reverted and probably this new methods should be introduced with new names.
We updated to the latest Bodi version 1.5 yesterday and are receiving this error when running tests:
OneTimeSetUp: System.MissingMethodException : Method not found: 'Void BoDi.ObjectContainer.RegisterTypeAs(System.String)'
Downgrading back to version 1.4.1 fixes the issue.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.