다음을 통해 공유


6 - Extending Unity

patterns & practices Developer Center

On this page: Download:
Introduction | Creating Custom Lifetime Managers | Lifetime Managers and Resolved Objects | Extending the SynchronizedLifetimeManager Type | Extending the LifetimeManager Type | Extending the Unity Container | Without the Event Broker Container Extension | With the Event Broker Extension | Implementing the Simple Event Broker | Implementing the Container Extension - Discovering the Publishers and Subscribers, Wiring Up the Publishers and Subscribers | Summary

Download code

Download PDF File

Order Paperback

Introduction

Although Unity is very flexible, there are scenarios where you might need to extend Unity in order to meet some specific requirements. Unity is extensible in many different ways, some of which you have already seen in the previous chapters of this guide, other extensibility options are covered in this chapter.

In Chapter 5, “Interception with Unity,” you saw how you can customize and extend the ways that Unity supports interception by:

  • Creating custom behaviors. Interception in Unity uses interception behaviors in a pipeline to implement your crosscutting concerns. You implement your custom behaviors by implementing the IInterceptionBehavior interface.
  • Creating custom policy injection matching rules. If you use policy injection to insert behaviors into the interception pipeline, you can add your own custom matching rules by implementing the IMatchingRule interface.
  • Creating custom policy injection call handlers. Again, if you use policy injection, you can create custom handler classes that enable you to define custom behaviors in the policy injection pipeline. You do this by implementing the ICallHandler interface.
  • Creating custom handler attributes. Associated with custom call handlers, you can also define custom attributes to decorate methods in your business classes to control the way that policy injection works in your application.

In this chapter, you’ll see how you can create custom lifetime managers and extend the Unity container. Chapter 3, “Dependency Injection with Unity,” describes the built-in lifetime managers such as the TransientLifetimeManager, the ContainerControlledLifetimeManager, and the HierarchicalLifetimeManager and how they manage the lifetime of the objects instantiated by the Unity container. In this chapter, you’ll see an example of how Unity implements a simple caching lifetime manager as a guide to creating your own custom lifetime managers.

By creating a Unity container extension, you can customize what Unity does when you register and resolve types. In this chapter, you’ll see an example of how you can add functionality to the container that automatically wires up event handlers when you resolve objects from the container.

Creating Custom Lifetime Managers

Chapter 3, “Dependency Injection with Unity,” describes how you can use Unity’s built-in lifetime managers to manage the lifetime of the objects that Unity creates when you resolve types registered with the container. For example, if you use the default TransientLifetimeManager lifetime manager, the container does not hold a reference to the resolved object and such objects should not, in general, hold on to any resources that must be disposed deterministically. If, on the other hand, you use the ContainerControlledLifetimeManager lifetime manager, the container itself tracks the object and is responsible for determining when the object it creates becomes eligible for garbage collection.

In addition to the seven built-in lifetime managers (ContainerControlledLifetimeManager, TransientLifetimeManager, PerResolveLifetimeManager, PerThreadLifetimeManager, ExternallyControlledLifetimeManager, PerRequestLifetimeManager and HierarchicalLifetimeManager), you can also create your own custom lifetime managers by extending either the abstract LifetimeManager class or the abstract SynchronizedLifetimeManager class in the Microsoft.Practices.Unity namespace.

You should extend the LifetimeManager class when your custom lifetime manager does not need to concern itself with concurrency issues: the built-in PerThreadLifetimeManager class extends the LifetimeManager class because it will not encounter any concurrency issues when it stores a resolved object in thread local storage. However, the built-in ContainerControlledLifetimeManager class could encounter concurrency issues when it stores a reference to a newly created object: therefore, it extends the SynchronizedLifetimeManager class.

You may find it useful to open the sample application, “CustomLifetimeManagers,” that accompanies this guide in Visual Studio while you read this section.

Lifetime Managers and Resolved Objects

Before examining an example of a custom lifetime manager, you should understand the relationship between lifetime managers and the objects resolved by the container. Given the following code sample, the container creates an instance of the ContainerControlledLifetimeManager type during the call to the RegisterType method.

IUnityContainer container = new UnityContainer();
container.RegisterType<Tenant>(new ContainerControlledLifetimeManager());

When you execute the following line of code, the container creates a new Tenant instance that is referenced by the ContainerControlledLifetimeManager instance.

var tenant = container.Resolve<Tenant>();

If you call the Resolve method a second time, the container returns a reference to the existing Tenant instance that is referenced by the ContainerControlledLifetimeManager instance.

Both the Tenant instance and the ContainerControlledLifetimeManager instance will remain in memory until the container itself is garbage collected by the runtime.

Extending the SynchronizedLifetimeManager Type

The example in this section shows how to create a custom lifetime manager that uses a cache to store the objects resolved by the container. It extends the SynchronizedLifetimeManager class because potentially two (or more) clients could try to store an instance of a type in the cache at the same time.

Note

The sample shows the basics of creating a custom lifetime manager. However, it does not show how to create a fully featured lifetime manager. For example, the code shown here does not support registering generic types.

This example defines a very simple interface that abstracts the logic of dealing with the cache from the lifetime manager. The following code sample shows this interface.

public interface IStorage
{
  object GetObject(string key);
  void StoreObject(string key, object value);
  void RemoveObject(string key);
}

The following code sample shows the implementation of the CachedLifetimeManager class that provides overrides of three methods from the SynchronizedLifetimeManager class.

public class CachedLifetimeManager
  : SynchronizedLifetimeManager, IDisposable
{
  private IStorage storage;
  private string key;

  public CachedLifetimeManager(IStorage storage)
  {
    this.storage = storage;
    this.key = Guid.NewGuid().ToString();
  }

  public string Key
  {
    get { return key; }
  }


  protected override object SynchronizedGetValue()
  {
    return storage.GetObject(key); 
  }

  protected override void SynchronizedSetValue(object newValue)
  {
    storage.StoreObject(key, newValue);
  }

  public override void RemoveValue()
  {
    Dispose();
  }

  public void Dispose()
  {
    Dispose(true);
    GC.SuppressFinalize(this); 
  }

  protected void Dispose(bool disposing)
  {
    if (disposing && storage != null)
    {
      storage.RemoveObject(key);
      storage = null;
    }
  }
}

The SynchronizedGetValue method could return a null if the object does not exist in the cache; in this scenario, the container instantiates a new object and then calls SynchronizedSetValue to add it to the cache.

Note

Nothing in Unity calls theRemoveValue method: it is shown here for completeness.

When Unity creates an instance of the SynchronizedLifetimeManager class, the constructor generates a GUID to use as the cache key for whatever object this instance SynchronizedLifetimeManager class is responsible for managing.

This example also provides a simple implementation of the IDisposable interface so that when the Unity container is disposed, the custom lifetime manager has the opportunity to perform any clean up; in this case, removing the resolved object from the cache.

The following code sample shows how you can use this custom lifetime manager. The SimpleMemoryCache class implements the IStorage interface.

class Program
{
  static void Main(string[] args)
  {
    IUnityContainer container = new UnityContainer();
    var cache = new SimpleMemoryCache();

    container.RegisterType<Tenant>(new CachedLifetimeManager(cache));
    var tenant = container.Resolve<Tenant>();
    var tenant2 = container.Resolve<Tenant>();
  }
}

The sequence diagram shown in Figure 1 summarizes how the container uses the custom lifetime manager.

Figure 1 - Container Interactions with a Custom Lifetime Manager

Figure 1 - Container Interactions with a Custom Lifetime Manager

The sequence diagram shows how the call to the RegisterType method results in a new CachedLifetimeManager instance. The first time you call the Resolve method, the CachedLifetimeManager instance does not find an instance of the requested type in the cache, so the Unity container creates an instance of the requested type and then calls SynchronizedSetValue to store the instance in the cache. Subsequent calls to the Resolve method result in the object being returned from the cache.

Extending the LifetimeManager Type

Extending the LifetimeManager class is almost the same as extending the SynchronizedLifetimeManager class. Instead of overriding the SynchronizedGetValue and SynchronizedSetValue methods, you override the GetValue and SetValue methods.

Extending the Unity Container

Unity enables you to create container extensions that add functionality to a container. Unity uses this extension mechanism to implement some of its own functionality such as interception: to start using interception, the first thing you must do is to add the container extension as shown in the following code sample.

using Microsoft.Practices.Unity.InterceptionExtension;

...

IUnityContainer container = new UnityContainer();
container.AddNewExtension<Interception>();

This section will show you an example of a container extension that can automatically wire up event handlers in objects that you resolve from the container. However, before seeing how to implement the container extension, you should understand what this extension does. This will make it easier for you to follow the details of the extension implementation later in this chapter.

Without the Event Broker Container Extension

The example uses a pair of very simple classes to illustrate the functionality of the extension. The two classes are the Publisher and Subscriber classes shown in the following code sample. These two classes use some basic event declarations.

class Publisher
{
  public event EventHandler RaiseCustomEvent;

  public void DoSomething()
  {
    OnRaiseCustomEvent();

  }

  protected virtual void OnRaiseCustomEvent()
  {
    EventHandler handler = RaiseCustomEvent;

    if (handler != null)
    {
      handler(this, EventArgs.Empty);
    }
  }
}

class Subscriber
{
  private string id;
  public Subscriber(string ID, Publisher pub)
  {
    id = ID;
    pub.RaiseCustomEvent += HandleCustomEvent;
  }

  public void HandleCustomEvent(object sender, EventArgs e)
  {
    Console.WriteLine(
      "Subscriber {0} received this message at: {1}", id, DateTime.Now);
  }
}

If you don’t have the event broker extension and you want to use Unity to instantiate a single Publisher instance wired to two Subscriber instances, then you have several options. For example, you could register and resolve the types as shown in the following code sample:

IUnityContainer container = new UnityContainer();
container
  .RegisterType<Publisher>(
    new ContainerControlledLifetimeManager())
  .RegisterType<Subscriber>(
    new InjectionConstructor("default", typeof(Publisher)));

  var pub = container.Resolve<Publisher>();
  var sub1 = container.Resolve<Subscriber>(
    new ParameterOverride("ID", "sub1"));
  var sub2 = container.Resolve<Subscriber>(
    new ParameterOverride("ID", "sub2"));

// Call the method that raises the event.
pub.DoSomething();

In this example, it’s important to use the ContainerControlledLifetimeManager lifetime manager when you register the Publisher class: this ensures that the container creates a single Publisher instance that the Resolve method returns and that the container passes to the Subscriber class constructor when resolving the Subscriber type. Resolving the two Subscriber instances uses a parameter override to pass different IDs to the two instances so that you can identify the output from each instance.

With the Event Broker Extension

With the event broker extension, it’s possible to simplify the implementation of the Subscriber class as shown in the following code sample.

You may find it useful to open the sample application, “EventBroker,” that accompanies this guide in Visual Studio while you read this section.

class Publisher
{
  [Publishes("CustomEvent")]
  public event EventHandler RaiseCustomEvent;

  public void DoSomething()
  {
    OnRaiseCustomEvent();

  }

  protected virtual void OnRaiseCustomEvent()
  {
    EventHandler handler = RaiseCustomEvent;

    if (handler != null)
    {
      handler(this, EventArgs.Empty);
    }
  }
}

class Subscriber
{
  private string id;

  public Subscriber(string ID)
  {
    id = ID;
  }

  [SubscribesTo("CustomEvent")]
  public void HandleCustomEvent(object sender, EventArgs e)
  {
    Console.WriteLine(
      "Subscriber {0} received this message at: {1}", id, DateTime.Now);
  }
}

In these new versions of the two classes, the event in the Publisher class is decorated with the Publishes attribute, and the handler method in the Subscriber class is decorated with the SubscribesTo attribute. In addition, the constructor in the Subscriber class is much simpler in that it no longer receives a reference to the Publisher class and no longer hooks up the event handler.

Dn178462.note(en-us,PandP.30).gifJana says:
Jana You have effectively decoupled the Subscriber class from the Publisher class. There is no longer a reference to the Publisher type anywhere in the Subscriber class.

The registration code is now also simpler.

IUnityContainer container = new UnityContainer();
container
  .AddNewExtension<SimpleEventBrokerExtension>()
  .RegisterType<Publisher>()
  .RegisterType<Subscriber>(new InjectionConstructor("default"));

  var sub1 = container.Resolve<Subscriber>(
    new ParameterOverride("ID", "sub1"));
  var sub2 = container.Resolve<Subscriber>(
    new ParameterOverride("ID", "sub2"));

// Call the method
pub.DoSomething();

The AddNewExtension method registers the container extension, which is custom class that extends the abstract UnityContainerExtension class. Registering the Publisher type now uses the default TransientLifetimeManager lifetime manager, and the Subscriber registration only needs to define how the ID value is passed to the constructor.

Implementing the Simple Event Broker

Now that you’ve seen what the event broker extension does, using attributes to define how the container should wire up event handlers and simplifying the registration of the two classes, it’s time to see how to implement the extension.

An EventBroker class tracks the event subscriber classes that subscribe to events in event publisher classes. The following code sample shows an outline of this class.

public class EventBroker
{
  ...
  
  public IEnumerable<string> RegisteredEvents
  {
    get 
    {
      ...
    }
  }

  public void RegisterPublisher(string publishedEventName,
    object publisher, string eventName)
  {
    ...
  }

  public void UnregisterPublisher(string publishedEventName,
    object publisher, string eventName)
  {
    ...
  }

  public void RegisterSubscriber(string publishedEventName,
    EventHandler subscriber)
  {
    ...
  }

  public void UnregisterSubscriber(string publishedEventName,
    EventHandler subscriber)
  {
    ...
  }

  public IEnumerable<object> GetPublishersFor(string publishedEvent)
  {
    ...
  }

  public IEnumerable<EventHandler> GetSubscribersFor(string publishedEvent)
  {
    ...
  }

  private PublishedEvent GetEvent(string eventName)
  {
    ...
  }
}

The EventBroker class uses the PublishedEvent class shown in the following code sample.

public class PublishedEvent
{
  private List<object> publishers;
  private List<EventHandler> subscribers;
  
  ...

  public IEnumerable<object> Publishers
  {
    get { ... }
  }

  public IEnumerable<EventHandler> Subscribers
  {
    get { ... }
  }

  public void AddPublisher(object publisher, string eventName)
  {
    ...
  }

  public void RemovePublisher(object publisher, string eventName)
  {
    ...
  }

  public void AddSubscriber(EventHandler subscriber)
  {
    ...
  }

  public void RemoveSubscriber(EventHandler subscriber)
  {
    ...
  }

  private void OnPublisherFiring(object sender, EventArgs e)
  {
    foreach(EventHandler subscriber in subscribers)
    {
      subscriber(sender, e);
    }
  }
}

The SimpleEventBroker extension must create and populate an EventBroker instance when you register and resolve types from the container that use the Publishes and SubscribesTo attributes.

Implementing the Container Extension

You may find it useful to open the sample application, “EventBroker,” that accompanies this guide in Visual Studio while you read this section.

The first step to implement a container extension is to extend the abstract UnityContainerExtension class and to override the Initialize method as shown in the following code sample.

public class SimpleEventBrokerExtension : 
  UnityContainerExtension, ISimpleEventBrokerConfiguration
{
  private readonly EventBroker broker = new EventBroker();

  protected override void Initialize()
  {
    Context.Container.RegisterInstance(broker);

    Context.Strategies.AddNew<EventBrokerReflectionStrategy>(
      UnityBuildStage.PreCreation);
    Context.Strategies.AddNew<EventBrokerWireupStrategy>(
      UnityBuildStage.Initialization);
  }

  public EventBroker Broker
  {
    get { return broker; }
  }
}

The Intialize method first registers an instance of the EventBroker class shown in the previous section with the container. The Initialize method then adds two new strategies to the container: an EventBrokerReflectionStrategy and an EventBrokerWireUpStrategy.

You can add strategies to the container that take effect at different stages in the container’s activities as it builds up an object instance. The EventBrokerReflectionStrategy strategy takes effect at the PreCreation stage: during this stage, the container is using reflection to discover the constructors, properties, and methods of the type currently being resolved. The EventBrokerWireUpStrategy strategy takes effect at the Initialization stage: during this stage, the container performs property and method injection on the object currently being instantiated by the container.

The following table summarizes the different stages where you can add your custom strategies to the container.

Stage

Description

Setup

The first stage. By default, nothing happens here.

TypeMapping

The second stage. Type mapping takes place here.

Lifetime

The third stage. The container checks for a lifetime manager.

PreCreation

The fourth stage. The container uses reflection to discover the constructors, properties, and methods of the type being resolved.

Creation

The fifth stage. The container creates the instance of the type being resolved.

Initialization

The sixth stage. The container performs any property and method injection on the instance it has just created.

PostInitialization

The last stage. By default, nothing happens here.

Note

If the container discovers a lifetime manager during the Lifetime phase that indicates that an instance already exists, then the container skips the remaining phases. You can see this happening in Figure 1, the sequence diagram that shows how the custom lifetime manager works.

The strategy classes that you add to the list of strategies at each stage all extend the BuilderStrategy class. In these strategy classes, you typically override the PreBuildUp method to modify the way that the container builds the object it is resolving, although you can also override the PostBuildUp method to modify the object after the container has completed its work and the PreTearDown and PostTearDown methods if you need to modify the way the container removes the object.

Discovering the Publishers and Subscribers

In the event broker example, during the PreCreation stage, EventBrokerReflectionStrategy strategy scans the type the container will create for events decorated with the Publishes attribute and methods decorated with the SubscribesTo attribute and stores this information in an EventBrokerInfoPolicy object as shown in the following code sample.

public class EventBrokerReflectionStrategy : BuilderStrategy
{
  public override void PreBuildUp(IBuilderContext context)
  {
    if (context.Policies.Get<IEventBrokerInfoPolicy>
      (context.BuildKey) == null)
    {
      EventBrokerInfoPolicy policy = new EventBrokerInfoPolicy();
      context.Policies.Set<IEventBrokerInfoPolicy>(policy, context.BuildKey);

      AddPublicationsToPolicy(context.BuildKey, policy);
      AddSubscriptionsToPolicy(context.BuildKey, policy);
    }
  }

  private void AddPublicationsToPolicy(NamedTypeBuildKey buildKey,
    EventBrokerInfoPolicy policy)
  {
    Type t = buildKey.Type;
    foreach(EventInfo eventInfo in t.GetEvents())
    {
      PublishesAttribute[] attrs =
      (PublishesAttribute[])eventInfo.GetCustomAttributes(
        typeof(PublishesAttribute), true);
      if(attrs.Length > 0)
      {
        policy.AddPublication(attrs[0].EventName, eventInfo.Name);
      }
    }
  }

  private void AddSubscriptionsToPolicy(NamedTypeBuildKey buildKey,
    EventBrokerInfoPolicy policy)
  {
    foreach(MethodInfo method in buildKey.Type.GetMethods())
    {
      SubscribesToAttribute[] attrs =
      (SubscribesToAttribute[])
      method.GetCustomAttributes(typeof(SubscribesToAttribute), true);
      if(attrs.Length > 0)
      {
        policy.AddSubscription(attrs[0].EventName, method);
      }
    }
  }
}

Wiring Up the Publishers and Subscribers

During the Initialization stage, the EventBrokerWireUpStrategy strategy retrieves the policy information stored by the EventBrokerReflectionStrategy strategy during the PreCreation stage and populates the EventBroker object with information about the events and publishers that the subscribers subscribe to. The following code sample shows how the EventBrokerWireUpStrategy strategy implements this final step in the event broker extension.

public class EventBrokerWireupStrategy : BuilderStrategy
{
  public override void PreBuildUp(IBuilderContext context)
  {
    if (context.Existing != null)
    {
      IEventBrokerInfoPolicy policy =
      context.Policies.Get<IEventBrokerInfoPolicy>(context.BuildKey);
      if(policy != null)
      {
        EventBroker broker = GetBroker(context);
        foreach(PublicationInfo pub in policy.Publications)
        {
          broker.RegisterPublisher(pub.PublishedEventName,
            context.Existing, pub.EventName);
        }
        foreach(SubscriptionInfo sub in policy.Subscriptions)
        {
          broker.RegisterSubscriber(sub.PublishedEventName,
          (EventHandler)Delegate.CreateDelegate(
          typeof(EventHandler),
          context.Existing,
          sub.Subscriber));
        }
      }
    }
  }

  private EventBroker GetBroker(IBuilderContext context)
  {
    var broker = context.NewBuildUp<EventBroker>();
    if(broker == null)
    {
      throw new InvalidOperationException("No event broker available");
    }
    return broker;
  }
}

Summary

This chapter described two ways that you can extend Unity. First, it described how to create custom lifetime managers that enable you to modify the way that Unity stores and tracks the instances of registered types created by the container. Second, it described how you can create container extensions that enable you to add custom behavior into the build-up process that occurs when the container resolves a type and creates a new instance.

Dn178462.note(en-us,PandP.30).gifCarlos says:
Carlos The Unity source code is a great reference for implementations of both lifetime managers and container extensions because Unity uses both of these mechanisms to implement some of its standard functionality.

Next Topic | Previous Topic | Home | Community