GithubHelp home page GithubHelp logo

bodi's People

Contributors

alexandrafung avatar darrencauthon avatar dependabot[bot] avatar gasparnagy avatar greybird avatar ibrahimbensalah avatar icnocop avatar jessicabuttigieg avatar leotsarev avatar mkmz avatar rdvanbuuren avatar sabotageandi avatar slawomir-brzezinski-at-interxion avatar socolin avatar tzongithub avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bodi's Issues

Support detection of circular dependencies in dynamic factory-based registrations

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.

Example

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.

Root cause analysis

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:

  • We begin with the outer resolve operation's resolution path:
    1. ClassWithCircularDependency1
    2. IObjectContainer
  • Then a new resolve operation - the inner one - begins a new resolution path
    1. ClassWithCircularDependency2
    2. ClassWithCircularDependency1
    3. IObjectContainer
  • Another new inner resolve operation begins, using a new path, and another, and another until the stack overflows

Proposed solution (overview)

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:

  1. ClassWithCircularDependency1
  2. IObjectContainer
  3. ClassWithCircularDependency2
  4. ClassWithCircularDependency1

At this stage, circular dependency detection may trigger and raise an exception, because ClassWithCircularDependency1 appears twice in the path.

Where to find an implementation

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.

Address TODO comment in code: FactoryRegistration.Resolve should add the created object into the pool of created objects

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?

Ability to update registered instance after it has been resolved

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.

IsRegistered<T> fails for registrations on parent containers

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.

Does instances created by registered factories are automatically disposed?

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,

Allows collection registration and resolution

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.

Error when open solution in MonoDevelop: Invalid configuration mapping

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:

  • Debug
  • LimitedRuntime_Debug
  • Release

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.

Propose moving away from 'single source file' as a design guideline

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.

Question: Container thread safety

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

ObjectContainer.Dispose() does not seem to be thread safe

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?

Add ability to check if a type/instance/interface is already registered or can be resolved

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:

  1. Pass the object in the [Before] method hook (i.e. dependency injection on the method).
    Currently throws an exception: System.Reflection.TargetParameterCountException: Parameter count mismatch.
  2. Pass the object in the constructor of the [Binding] class.
    If the object is not registered, currently throws an exception: 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.

Symbol index could not be retrieved

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".

Allow registration of types where ctor have parameters

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"));

Question: Code structure

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,

Formally adopt semantic versioning and ensure all NuGet releases are appropriately tagged

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.

Add additional nuget package metadata

Summary

I wish for the nuget packages to have the licence expression property set correctly and the repository url

Details

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.

Exception when registering types defined in the .NET Standard libraries

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)

Receiving Error with Upgrade to Bodi 1.5 via NuGet

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.