Extreme ASP.NET Makeover: Mr. Escher, Your Software is Ready - Circular Dependency Cycle

Download

Right click “Save as…”

  • High Quality WMV (PC)
  • MP3 (Audio only)
  • MP4 (iPhone, Android)
  • Mid Quality WMV (Lo-band, Mobile)
  • WMV (WMV Video)

Extreme ASP.NET Makeover: Mr. Escher, Your Software is Ready –Circular Dependency Cycle

The only reason for Users to reference Host is to raise events on behalf of the Users class. We can quickly and easily remove this dependency by allowing Users to raise its own events, as shown in Figure 4.

Figure 4 Users Raises Its Own Events

public class Users : IUsers {
  public event 
    EventHandler<UserAccountActivityEventArgs> UserAccountChanged;

  private void OnUserAccountChanged(UserInfo user,
    UserAccountActivity activity) {
      if(UserAccountChanged != null) {
        UserAccountChanged(this, 
          new UserAccountActivityEventArgs(user, activity));
      }
  }

  public event 
    EventHandler<UserGroupActivityEventArgs> UserGroupChanged;

  private void OnUserGroupChanged(UserGroup group,
    UserGroupActivity activity) {
    if(UserGroupChanged != null) {
      UserGroupChanged(this,
        new UserGroupActivityEventArgs(group, activity));
    }
  }
}

Host can now subscribe to the Users.UserAccountChanged and Users.UserGroupChanged events and re-broadcast those events to interested listeners without breaking the facade by exposing the Users class directly, as Figure 5 shows.

Figure 5 Host Can Subscribe to the Users.UserAccountChanged and Users.UserGroupChanged Events

public class Host : IHostV30 {
    public Host(ISettings settings, IUsers users) {
        customSpecialTags = new Dictionary<string, CustomToolbarItem>(5);
        this.settings = settings;
        this.users = users;
        users.UserAccountChanged += OnUserAccountActivity;
        users.UserGroupChanged += OnUserGroupActivity;
    }

    public event EventHandler<UserAccountActivityEventArgs> UserAccountActivity;

    private void OnUserAccountActivity(object sender,
      UserAccountActivityEventArgs args) {
        if(UserAccountActivity != null) {
            UserAccountActivity(this, args);
        }
    }

    public event EventHandler<UserGroupActivityEventArgs> UserGroupActivity;

    private void OnUserGroupActivity(object sender, 
      UserGroupActivityEventArgs args) {
        if(UserGroupActivity != null) {
            UserGroupActivity(this, args);
        }
    }
}

Users no longer needs a reference to Host and it can be safely removed from Users’ constructor. We have broken the circular dependency between Host and Users. Running our tests, we see that everything is green again.

Before we move on, notice how the circular dependency problem wasn’t immediately noticeable when the classes were linked together using Singletons. Singletons, due to their global nature, make it easy for dependencies to spill between classes. Using the dependency injection principle, it is much easier to see a class’s dependencies because most of them are in the constructor and the remainder are method parameters.

Don’t You Forget about IoC

With apologies to Billy Idol, we cannot forget about the container. It is responsible for creating and wiring together our dependencies. Looking at ScrewTurnWikiInitializationTask, we see the following:

Users.Instance = new Users();

Classes that take a dependency on IUsers are going to get an instance of Users supplied through their constructor, but classes that access Users.Instance are going to get a different Users instance. We can solve this minor problem by allowing the IoC container to supply ScrewTurnWikiInitializationTask with an IUsers instance through its constructor. We now have:

public ScrewTurnWikiInitializationTask(IHostV30 host, 
         ISettingsStorageProviderV30 settingsStorageProvider, IUsers users) {
    this.host = host;
    this.settingsStorageProvider = settingsStorageProvider;
    this.users = users;
}

public void Execute() {
    ...
    Users.Instance = users;
    ....
}

While we’re talking about the container, let’s look at HostAnProviderRegistration:

public class HostAndProviderRegistration : IContainerStartupTask {
    public void Execute(IWindsorContainer container) {
        container.Register(
            Component.For<IHostV30>().ImplementedBy<Host>(),
            Component.For<ISettings>().ImplementedBy<Settings>(),
            Component.For<IUsers>().ImplementedBy<Users>());

        ...
    }
}

You’ll notice that we’re starting to get a lot of repetitive code. This is a great place to establish a convention rather than manually configuring each dependency. Let’s create a ScrewTurn.Wiki.Hosting namespace and register each dependency in that namespace against its first interface:

public class HostAndProviderRegistration : IContainerStartupTask {
    public void Execute(IWindsorContainer container) {
        container.Register(
            AllTypes.FromAssembly(Assembly.GetExecutingAssembly())
                .Where(x => x.Namespace.StartsWith("ScrewTurn.Wiki.Hosting"))
                .WithService.FirstInterface()
        );
        ...
    }
}

As we refactor dependencies, we need only place them in this namespace (achieved by moving them to the Hosting folder in ScrewTurn.Wiki.Core) and they will automatically be registered correctly in the IoC container.

The Journey Continues

Users depends on the Settings.Instance and Pages.Instance Singletons. These are quickly extracted into constructor dependencies as before. Pages gets the same treatment as Users—moving to ScrewTurn.Wiki.Hosting namespace (for automatic IoC registration through our convention), interface extraction, and pushing dependent Singletons to constructor parameters. (Additional tests were added to ensure that IPages can be resolved from the IoC container and that Pages.Instance is properly initialized.)

Pages has the same problem as Users in that it uses Host to raise events on its behalf. The same procedure is applied whereby Pages is refactored to raise its own events, which Host then registers for and re-raises to interested listeners. Once again, we’ve broken an unnecessary coupling between two classes, Pages and Host this time. For those interested in the details, you can check out the code download associated with the article.

Pages makes use of many other Singletons and the refactoring continues in much the same vein until we get to Users. Once we move Users into a constructor parameter, our tests break. Pages depends on Users and Users depends on Pages. We’ve uncovered another circular dependency issue. Let’s take a closer look.

Other videos from this article

Read the full article at http://msdn.microsoft.com/magazine/ee470637.aspx

Tags:

Follow the Discussion

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.