次の方法で共有


Converting the Composite Web Application Block to Unity - Clean Up ICompositionContainer

This is the third post in a series. The other post include

If you want background, go read the earlier posts.

Based upon feedback, I am making the source code available at CWAB and Unity.

In the last post, we started getting closer to my eventual goal. However, we still need to get rid of the static methods, Builder, Locator, Containers, and Services. 

Why?

  • Static methods will make the necessary refactorings a pain in the.... well, a pain.
  • Builder and Locator are handled by Unity internally, and we no longer need to deal with them directly. 
  • Containers will just be registered in the root container by module name, and we can use the WebClientApplication.FindModuleContainer method when we need to. 
  • Then there are CWAB Services.  These services will probably just go away.  Global Services are types that are Registered on the Root Container as singletons, and module services are types registered on the appropriate Container as singletons.  This will be really simple once we get Unity tied into the solution. 

Once we get rid of these items on the interface, we will want to add a few from the IUnityContainer, namely CreateChildContainer, and at least a few overloads (if not all of them) for RegisterType, RegisterInstance, and Resolve.

Then, we will add a new type of container to the project, a CWABUnityContainer, and maybe a CompositionContainer factory.  The CWABUnityContainer will be a facade that implements ICompositionContainer and hides a real UnityContainer under the hood.  After that, things should be simple, allowing us to delete the old container and its code, and then do a bit of re-organizing.

Note: Since this is a proof of concept, not what I would call "shipping code", I have turned off creating XML comments in the solution.  With the volume of changes, I was getting annoyed with all the warnings.

Removing Static Methods from CompositionContainer

There is only one static method on CompositionContainer, and it relies on the Locator property of the container object parameter to work.  In removing this, we can simplify the type, and remove the only dependency on the Locator property.  Let's change BuildItem to be a public, non-static method, and add it to the ICompositionContainer interface for a little while.

Everything compiles.  Tests green (except for the ones I had commented out near the end of the last article.

Removing ObjectBuilder specific parts of the interface

Let's do these one at a time, starting with Builder.  Comment it out in the interface, and....

Two parts of the test fixture fail to compile:

  • CreateRootContainerInitializesContainer needs a line commented out, since we changed the semantics of the method
  • MockWebClientApplication needs a change to the ApplicationBuilder property to return null for the moment, with a comment to come back later.

Now we can compile, and run the tests, and we are green. However, I did notice something that I want to take care of before I go any further.... IWebClientApplication has an ApplicationBuilder and a PageBuilder, which can go away.  The Container will handle the responsibilities of these two properties.  If we delete these properties from the interface, the MockWebClientApplication and WebClientApplication, we get some compile errors in WebClientApplication, which lead us down an ugly path. Instead, we will leave these properties ONLY in WebClientApplication, and make a note to delete them later.

Compile -> Good. 

Tests -> Green.

Next Step, the Locator...

If we remove it from the interface, we have compilation problems in WebClientApplication.BuildItemWithCurrentContext where we call BuildItem.  Let's roll back, change the signature of BuildItem to not have a locator, and the implementation to use the current container's locator, and then re-do the change.  Again, we will leave the property only on the CompositionContainer, even though it is not part of the interface, and add a few comments.  After a quick change to the test CreateRootContainerInitializesContainer, everything is green, and all tests pass. 

The Containers collection

If we remove the Containers property from the ICompositionContainer interface, we have problems in the ModuleLoaderService.  The ModuleLoaderService understands how the CompositionContainer works, and can add new containers (one for each module) to the collection.  We also have challenges in the DefaultModuleContainerLocatorService, where we need to find a container in the collection.  If we think about how Unity works, both of these problems can go away if we find another another way to get child containers, and to create child containers.  Unity has the Resolve method, which can take a name parameter to get a named instance of an object (this handles getting a child container). We also have the CreateChildContainer method, to help with the creation, as well as RegisterInstance to give a created instance a name to use later.

Let's roll back removing the Containers property, add the RegisterInstance and Resolve methods (that use a name) to the interface, test them, then add the CreateChildContainer method to the interface, and test it.  Once we have those pieces, we can re-work ModuleLoaderService and DefaultModuleContainerLocatorService to use the new methods.  Once we do all that, we can remove Containers from the ICompositionContainer interface without problems.

So, let's start with RegisterInstance.  Since I know whatever implementation I come up with is a throw away implementation, and it only needs to work well enough NOT to break the existing code, I am going to keep this simple. 

First, a unit test added to CompositionContainerFixture:

 [TestMethod]
public void CanRegisterInstance()
{
    TestableRootCompositionContainer root = new TestableRootCompositionContainer();
    root.RegisterInstance(typeof(string), "foo", "bar");
    string returned = (string) root.Resolve(typeof (string), "foo");
    Assert.AreEqual("bar", returned);
}

This tests for the behavior that Unity will have.  I will add the following to the ICompositionContainer to get it to compile:

 void RegisterInstance(Type t, string name, object instance);

And then add the stupid implementation:

 public void RegisterInstance(Type t, string name, object instance)
{
}

Everything compiles, and the test fails, horribly.

Now, to get the test to pass, RegisterInstance becomes:

 private Dictionary<string, object> _registeredInstances = new Dictionary<string, object>();
public void RegisterInstance(Type t, string name, object instance)
{
    _registeredInstances.Add(String.Concat(t.FullName, name), instance);
}

Now, I know that is an ugly, bad, horrible monstrosity of an implementation.  It is also the simplest thing that will work. :-)  I also know I could use OB to do this, but again, I am keeping things simple, knowing I will throw away the implementation in a few hours.

Resolve changes a bit too.  I added an overloaded version, and then made the unit test above pass by making the code look like this:

 public object Resolve(Type typeOfItem)
{
    string temporaryID = Guid.NewGuid().ToString();
    return Resolve(typeOfItem, temporaryID);
}

public object Resolve(Type typeOfItem, string name)
{
    string key = String.Concat(typeOfItem.FullName, name);
    if (_registeredInstances.ContainsKey(key))
    {
        return _registeredInstances[key];
    }
    
    PolicyList policies = new PolicyList();
    policies.Set<ISingletonPolicy>(new SingletonPolicy(false), typeOfItem, name);
    policies.Set<ICreationPolicy>(new DefaultCreationPolicy(), typeOfItem, name);
    policies.Set<IPropertySetterPolicy>(new PropertySetterPolicy(), typeOfItem, name);

    return _builder.BuildUp(
        _locator,
        typeOfItem,
        name,
        null,
        policies);
}

Compile.  Run Tests.  We are Green.

Now to add CreateChildContainer.  First, we need a unit test:

 [TestMethod]
public void CanCreateChildContainer()
{
    ICompositionContainer container = new TestableRootCompositionContainer();
    
    ICompositionContainer child = container.CreateChildContainer();
    Assert.IsNotNull(child);
    Assert.AreNotSame(container, child);
}

To make that compile and then pass, I added CreateChildContainer to the interface, the class, and then implemented it.  Simple.  And we are Green again.

Next step: remove from ICompositionContainer the Containers property, compile, and it fails.  This requires minor tweaking to the ModuleLoaderService and DefaultModuleContainerLocatorService. Now, we compile.  However, the Containers property is still on the CompositionContainer.  After we remove it from there, we have to fix a lot of unit tests so that they compile (and removing a few that no longer make sense, semantically).  This is simple, but tedious.  <click> <click> <swearing> <click> <click> ...

Ok, we are now compiling and all unit tests pass.

I need a break, so we will cut this a little shorter than I had hoped.

In this installment I wanted to remove the following from the ICompositionContainer:

  • static methods
  • Builder
  • Locator
  • Containers
  • Services

We completed the first four. In the next installment, we will remove the Services collection, replacing it with using RegisterInstance and Resolve. After that, we will compare our ICompositionContainer to IUnityContainer, see what else we need to do, and may even pull in Unity.

Comments

  • Anonymous
    April 09, 2008
    PingBack from http://upcomingcamera.info/?p=21216

  • Anonymous
    April 22, 2008
    This is the third post in a series. The other post include Converting the Composite Web Application Block

  • Anonymous
    May 01, 2008
    This is the fifth post in a series. The other post include: Converting the Composite Web Application

  • Anonymous
    May 21, 2008
    This is the sixth post in a series. The other post include: Converting the Composite Web Application