Wednesday, February 03, 2010

10 Advanced Windsor Tricks – 8. Dependency graph visualisation

Here’s part eight of (at least) 10 Advanced Windsor Tricks.

A frequent complaint people have when they first get to grips with IoC Containers is that they find it hard to tell how their application composes at runtime. What’s needed is a clear description of the tree of dependencies in your application. This dependency ‘graph’ is built at registration time and Windsor exposes its dependency graph via the GraphNodes collection on the Kernel. The following class recursively walks down the GraphNodes tree and outputs the application’s complete component graph:

using System;
using System.IO;
using Castle.Core;
using Castle.Windsor;

namespace Suteki.Common.Windsor
{
    public class DependencyGraphWriter
    {
        private readonly IWindsorContainer container;
        private TextWriter writer;

        public DependencyGraphWriter(IWindsorContainer container, TextWriter writer)
        {
            this.container = container;
            this.writer = writer;
        }

        public void Output()
        {
            var graphNodes = container.Kernel.GraphNodes;

            foreach (var graphNode in graphNodes)
            {
                if (graphNode.Dependers.Length != 0) continue;
                Console.WriteLine();
                WalkGraph(graphNode, 0);
            }
        }

        private void WalkGraph(IVertex node, int level)
        {
            var componentModel = node as ComponentModel;
            if (componentModel != null)
            {
                writer.WriteLine("{0}{1} -> {2}", 
                    new string('\t', level), 
                    componentModel.Service.FullName,
                    componentModel.Implementation.FullName);
            }

            foreach (var childNode in node.Adjacencies)
            {
                WalkGraph(childNode, level + 1);
            }
        }
    }
}

I usually have a test fixture that asserts various container registrations. Using the class above it’s easy to add a little explicit test that outputs the application’s graph:

[Test, Explicit]
public void Output_dependency_graph()
{
    var dependencyGraphWriter = new DependencyGraphWriter(container, Console.Out);
    dependencyGraphWriter.Output();
}

Here’s an small part of the output for Suteki Shop:

Suteki.Shop.Controllers.OrderStatusController -> Suteki.Shop.Controllers.OrderStatusController
    Suteki.Common.Repositories.IRepository`1 -> Suteki.Common.Repositories.Repository`1
        Suteki.Common.Repositories.IDataContextProvider -> Suteki.Common.Repositories.DataContextProvider
            Suteki.Common.Repositories.IConnectionStringProvider -> Suteki.Common.Repositories.ConnectionStringProvider
    Suteki.Shop.Services.IUserService -> Suteki.Shop.Services.UserService
        Suteki.Common.Repositories.IRepository`1 -> Suteki.Common.Repositories.Repository`1
            Suteki.Common.Repositories.IDataContextProvider -> Suteki.Common.Repositories.DataContextProvider
                Suteki.Common.Repositories.IConnectionStringProvider -> Suteki.Common.Repositories.ConnectionStringProvider
        Suteki.Shop.Services.IFormsAuthentication -> Suteki.Shop.Services.FormsAuthenticationWrapper
    Suteki.Shop.Services.IEmailService -> Suteki.Shop.Services.EmailService
        Suteki.Common.Services.IEmailBuilder -> Suteki.Common.Services.EmailBuilder
        Suteki.Common.Services.IEmailSender -> Suteki.Common.Services.EmailSenderLogger
            Suteki.Common.Services.IEmailSender -> Suteki.Common.Services.NullEmailSender
            Castle.Core.Logging.ILogger -> Castle.Services.Logging.Log4netIntegration.Log4netLogger
        Suteki.Shop.Services.IBaseControllerService -> Suteki.Shop.Services.BaseControllerService
            Suteki.Common.Repositories.IRepository`1 -> Suteki.Common.Repositories.Repository`1
                Suteki.Common.Repositories.IDataContextProvider -> Suteki.Common.Repositories.DataContextProvider
                    Suteki.Common.Repositories.IConnectionStringProvider -> Suteki.Common.Repositories.ConnectionStringProvider

2 comments:

Anonymous said...

Hi Mike, this code didn't seem to give the output that you showed. I had to change a couple of things to make it work:

public class DependencyGraphWriter
{
private readonly IWindsorContainer _container;
private readonly TextWriter _writer;

public DependencyGraphWriter(IWindsorContainer container, TextWriter writer)
{
_container = container;
_writer = writer;
}

public void Output()
{
var graphNodes = _container.Kernel.GraphNodes;

foreach (var graphNode in graphNodes)
{
_writer.WriteLine();
WalkGraph(graphNode, 0);
}
}

private void WalkGraph(GraphNode node, int level)
{
var componentModel = node as ComponentModel;
if (componentModel != null)
{
_writer.WriteLine(
"{0}{1} -> {2}",
new string('\t', level),
componentModel.ComponentName,
componentModel.Implementation.FullName);
}

foreach (var childNode in node.Dependents)
{
WalkGraph(childNode, level + 1);
}
}
}

BS said...

I encountered a problem where one of Dependents was node itself and the graph traversal was locking in infinite loop. The solution was to rewrite WalkGraph method into following:


private void WalkGraph(IVertex node, int level)
{
var componentModel = node as ComponentModel;
if (componentModel != null)
{
writer.WriteLine("{0}{1} -> {2}",
new string('\t', level),
componentModel.Service.FullName,
componentModel.Implementation.FullName);
}

foreach (var childNode in node.Adjacencies)
{
if(childNode != node)
WalkGraph(childNode, level + 1);
}
}