Unity v.1.0 = Lots of Busted Code.

Apr 7, 2008 at 2:14 PM
Edited Apr 7, 2008 at 2:14 PM
Alrighty! I just upgraded my codebase to 1.0. Out of 50 tests that were passing, 23 of them made it over, LOL.

So I'm trying to track down the problem. I using using a lot of policies that set object's IBuildPlanPolicy to an override policy. It appears somewhere along the way that these policies get replaced by a DependencyResolverTrackerPolicy. So that when the BuildPlanStrategy runs, the IBuildPlanPolicy that was registered to that object returns null.

Any idea what is happening here and why this is different from previous versions?

Thanks as always for any insight that you can provide!
Apr 7, 2008 at 2:27 PM
Edited Apr 7, 2008 at 2:30 PM
Ok, I found the problem, and it's ALL MY FAULT. ;) This is actually based off the recommendation for flushing the build policies after a call into InjectedMembers.ConfigureInjectionFor:

 
        public InjectedMembers ConfigureInjectionFor(Type typeToInject, string name, params InjectionMember[] injectionMembers)
        {
            foreach(InjectionMember member in injectionMembers)
            {
                member.AddPolicies(typeToInject, name, Context.Policies);
            }
 
            // Flush the build plan and current resolvers so that it will get rebuilt on next resolve.
            NamedTypeBuildKey buildKey = new NamedTypeBuildKey(typeToInject, name);
            Context.Policies.Clear<IBuildPlanPolicy>(buildKey);
            DependencyResolverTrackerPolicy.RemoveResolvers(Context.Policies, buildKey);
            return this;
            
        }

In my case, I am setting a specific build policy. This policy clear should only be for DynamicMethodBuildPlan's, since they are the ones that get cached and need to be flushed. All other policies should stay in place.

Or actually, flushing the policy THEN calling InjectionMember.AddPolicies will work just as well.
Apr 7, 2008 at 7:48 PM
That does it, I'm never listening to you again. ;-)

Umm, ALL build plans are cached in the policy list, so I'm not sure what you're really doing. And the DependencyResolverTrackerPolicy is an independent thing, so I don't understand your comment about it "replacing build plans" from above.

What are you trying to accomplish here?

-Chris
Apr 7, 2008 at 8:21 PM
Edited Apr 7, 2008 at 8:25 PM

ctavares wrote:
What are you trying to accomplish here?


Put this test in StaticFactoryFixture, make it pass, and you fix the problem. :)

[TestMethod]
public void ConfiguredFactoryDelegateGetsCalledAfterInjectedMembersConfigured()
{
    bool factoryWasCalled = false;
 
   IUnityContainer container = new UnityContainer()
        .AddNewExtension<StaticFactoryExtension>()
        .Configure<IStaticFactoryConfiguration>()
            .RegisterFactory<MockLogger>(
                delegate
                {
                    factoryWasCalled = true;
                    return new MockLogger();
                })
            .Container
        .RegisterType<ILogger, MockLogger>()
		.Configure<InjectedMembers>().ConfigureInjectionFor<MockLogger>().Container;
    ILogger logger = container.Resolve<ILogger>();
    Assert.IsInstanceOfType(logger, typeof(MockLogger));
    Assert.IsTrue(factoryWasCalled);}
Apr 8, 2008 at 5:54 AM
But I don't want that test to pass.

Registering a static factory says "I'm taking over all construction and injection." Configuring injected members says "Please build me the usual way, and in the process use these parameters." As usual with Unity configuration, last one in wins.

I suspect there's another abstraction or extension point trying to sneak out here, but I don't see what it is yet.
Apr 8, 2008 at 9:00 PM
Could it be that Constructor injection should be separated from Property/Method injection?

The same way we can do a build up on an existing instance, we could be able to use StaticFactoryExtension to build the instance and InjectedMembers extension to inject other dependencies.

Thoughts?
Apr 8, 2008 at 10:00 PM


ctavares wrote:
Registering a static factory says "I'm taking over all construction and injection." Configuring injected members says "Please build me the usual way, and in the process use these parameters." As usual with Unity configuration, last one in wins.


I'm using the StaticFactory as an example. If you can imagine another extension that does the same thing (sets a policy that implements IBuildPlanPolicy within the AddToPolicies call), then it gets nuked the first thing out of that forloop.

So the problem here is that I'm setting IBuildPlanPolicy policies in that for loop. So that when that forloop exits, then they get cleared. If you put the call to IPolicyList.Clear BEFORE that forloop, the problem goes away (right now Unity Contributions is using a "special sauce" build that does exactly this).

However, I again stress that we should really only be clearing the policies that have been pre-compiled by a build plan...
Apr 8, 2008 at 10:44 PM
So your InjectionMember instances are actually adding IBuildPlan policies? Interesting approach.

It's easy enough to move the clear to before the loop, and doesn't appear to hurt anything.
Apr 9, 2008 at 12:12 AM


ctavares wrote:
So your InjectionMember instances are actually adding IBuildPlan policies? Interesting approach.



I could be butchering the way things work (wouldn't be the first time!). I was under the impression that setting an IBuildPlanPolicy to a type is the way to override default construction.

As for breaking apart constructor injection vs. method/property injection... I don't have too stong an opinion on it, other than to say that keeping everything under one roof is more convenient. It could prove beneficial, though (how's that for a non-commital?).
Apr 9, 2008 at 1:03 AM
Setting an IBuildPlanPolicy takes over construction, property setting, and method injection. Basically, you replace the generated IL with your own code, and you're responsible for doing whatever you want.

If you want to instead say "call this constructor instead" or "Use these parameter values" then you drop into the selector and resolver policies.
Apr 9, 2008 at 1:59 AM


ctavares wrote:
If you want to instead say "call this constructor instead" or "Use these parameter values" then you drop into the selector and resolver policies.



I'm actually just wanting to create the object. For instance, I have one policy that creates an object through serialization (from an AssemblyEmbeddedResource -- it's pretty cool. :) ), while another policy checks from an ADO.NET Entity Framework ObjectContext to retrieve the object. "That's really all I'm looking to do: Create the object THIS way, but do EVERYTHING else exactly how you normaly would."

It sounds like with specifiying an IBuildPlanPolicy, I'm overriding more functionality than I intend (even though I haven't encountered this, most likely through sheer luck). With that in mind, what's the best way to go about telling the UnityContainer to just create my object a certain way but do everything else as it is configured?

Thank you for being patient in helping me unravel this best practice!
Apr 9, 2008 at 6:37 AM
Ok, I see what you're talking about.

Here's the thing that I think will get you working: the construction code in the build plan checks for an existing object. If there is one, it skips calling the constructor.

So I think what you should do is add a new strategy or strategies BEFORE the build plan, and construct your object there. Set the Existing object in the IBuilderContext, and then let it go down the chain. This way construction happens however you want it to, but properties and methods still get injected as normal.
Apr 9, 2008 at 4:35 PM


ctavares wrote:
So I think what you should do is add a new strategy or strategies BEFORE the build plan, and construct your object there. Set the Existing object in the IBuilderContext, and then let it go down the chain. This way construction happens however you want it to, but properties and methods still get injected as normal.


Argggggh... Yet another strategy to introduce? If at all possible, I'd like to suggest making this a built-in strategy. Sorta like built-in lifetime management: It's so much... cleaner. :)

But cool. I'll do exactly that. Problem solved.