Udostępnij za pośrednictwem


Creating and Referencing Enterprise Library Objects

This topic discusses the ways you can instantiate and access Enterprise Library objects, the advantages and disadvantages of each approach, and more details about how they work in the underlying code. Enterprise Library offers a great deal of functionality, and many different ways to access it. This topic is intended to clarify the choices and to help you determine what will work best for your application.

Typically, you will create instances of Enterprise Library objects using one of the following two approaches:

  • Using the Unity Service Locator. This is the simplest approach, and is recommended for simple applications that have few dependencies, and where you do not want to take advantage of contemporary architectural patterns such as dependency injection. It requires no initialization or setup. You simply configure your application to use Enterprise Library and then call the methods of the service locator to obtain instances of Enterprise Library types on demand.
  • Accessing the Unity Container Directly. This more sophisticated approach allows you to obtain the full benefits of contemporary architectural patterns such as dependency injection for your layers, components, and custom types. It requires only minimal setup, but may require that you maintain a reference to the container in your application.

When you use either of these approaches, you will typically request and obtain references to one or more non-static objects and interfaces that are part of each application block, which allow you to access the functionality of the blocks and obtain instances of Enterprise Library objects in your code using both dependency injection and the service locator approach. For a list of these objects and interfaces, see Non-Static Instances and Instance Factories.

Other approaches to creating Enterprise Library objects that you may choose include:

  • Using a different service locator if you decide to use a container other than Unity. For more information about how the service locator works, see Initializing and Setting the Current Container. For more information about replacing the default Unity container, see The Dependency Injection Model in the section "Design of Enterprise Library".
  • Using the legacy static facades and factories that were the default approach in previous versions of Enterprise Library. For more information about these, see Legacy Static Facades and Factories.

Using the Unity Service Locator

The simplest way to instantiate Enterprise Library objects is to use a service locator to specifically create instances of objects on demand, instead of injecting them into your application objects. A service locator is effectively a wrapper for the container, and exposes a series of methods you can use to return an instance of the appropriate type based on registrations and mappings in the container. The following table lists the advantages and disadvantages of using the service locator approach to instantiate objects.

Advantages

Disadvantages

Consistency: There is one way to get any Enterprise Library object, and it is the same across every block. You just need to know what type you have to instantiate to get access to the block.

Change in style: Rather than just calling into static methods when you want to use Enterprise Library, the code works better if you create the Enterprise Library objects early and maintain a reference to them, rather than recreating them every time.

Testability: Having instances rather than static objects means it is easier to write testable code; you can replace the Enterprise Library objects with fake objects reasonably easily.

Flexibility: This approach allows you to resolve the instance factories that are not available through the legacy static facades.

The EnterpriseLibraryContainer has a Current property that exposes the service locator. This service locator contains the GetInstance method and overloads that provide the mechanism to instantiate objects; in this case Enterprise Library objects defined in the application configuration. You do not need to instantiate the container. The primary disadvantage to this approach is that you must manually create the objects, and pass them into your target objects to satisfy dependencies.

Service Locator Examples

The following example uses the GetInstance method to create a LogWriter instance from the Logging Application Block.

// Create a LogWriter object.
LogWriter writer = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();

// Use the LogWriter object.
writer.Write("My message");
'Usage
' Create a LogWriter object.
Dim  writer As LogWriter = EnterpriseLibraryContainer.Current.GetInstance(Of LogWriter)()
 
' Use the LogWriter object.
writer.Write("My message")

The following example uses GetInstance to access a named database from the Data Access Application Block.

Database db = EnterpriseLibraryContainer.Current.GetInstance<Database>("mydatabase");
db.ExecuteNonQuery("DoSomethingInteresting");
'Usage
Dim db As Database =  EnterpriseLibraryContainer.Current.GetInstance(Of Database)("mydatabase") 
db.ExecuteNonQuery("DoSomethingInteresting")

The following example uses the GetInstance method to create the instances of LogWriter and ExceptionManager and then passes them to the constructor of a class named TaxCalculator (not shown here).

LogWriter lw = EnterpriseLibraryContainer.Current.GetInstance<LogWriter>();
ExceptionManager em = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();
TaxCalculator tc = new TaxCalculator(em, lw);
'Usage
Dim lw As LogWriter = EnterpriseLibraryContainer.Current.GetInstance(Of LogWriter)()
Dim em As ExceptionManager = EnterpriseLibraryContainer.Current.GetInstance(Of ExceptionManager)()
Dim tc As New TaxCalculator(em, lw)

For information about the types of Enterprise Library objects that you can obtain through the service locator, see Non-Static Instances and Instance Factories.

Note

EnterpriseLibraryContainer.Current is a writable property. For more information on how and why to change this property, see Initializing and Setting the Current Container.

Accessing the Unity Container Directly

One powerful feature Enterprise Library enables you to use is a higher-level architectural pattern called Inversion of Control and a subset of this pattern called Dependency Injection. The basic principle is that, instead of retrieving the objects you need, you receive them as inputs. A container, which is basically just a mechanism for storing type registrations and mappings and instantiating the requested type, is configured to return an instance of the appropriate type when your application code requests (or resolves) an interface, a base class, or a concrete type by defining a dependency on that type. The following table shows advantages and disadvantages of using the dependency injection approach to instantiate objects.

Advantages

Disadvantages

Testability: It is trivial to isolate classes from dependencies when using the dependency injection style.

Architectural changes: Your application must be designed in the dependency injection style.

Decoupling: dependency injection style applications typically have much looser coupling, resulting in better maintainability overall.

Complexity: A dependency injection container is often considered overkill for small projects.

Integration with other frameworks: Many other features are often supplied inside dependency injection containers that go beyond just dependency injection; method interception is one example. This lets you take advantage of these other features as well as Enterprise Library.

Familiarity: Learning the dependency injection style and container will take a little time.

When using dependency injection you do not explicitly request an instance; rather, the dependency injection mechanism determines the instance that is needed and injects it. Using dependency injection relieves you of the need to manually create all the dependent object instances and pass them into the target objects. The job of a dependency injection container is to create instances for you, determine the dependencies, and wire everything up appropriately.

If you have embraced the dependency injection architectural style in your applications, or you want to be able to access the container to manage its lifetime or to create and modify type registrations and mappings, you can specifically create a container and configure it. This means that you can resolve the other objects that your application requires, such as user interface components, view models, and business objects, in addition to the Enterprise Library objects.

There are many advantages to this style, including easier testability and making dependencies more obvious. The documentation for Enterprise Library focuses on using dependency injection and the default Unity container.

Enterprise Library provides the EnterpriseLibraryCoreExtension container extension to make it easy to initialize and populate the default Unity container with the Enterprise Library configuration information. The following example instantiates a new Unity container and adds the extension to it that makes all of the Enterprise Library registrations and mappings available.

var container = new UnityContainer().AddNewExtension<EnterpriseLibraryCoreExtension>();
'Usage
Dim container = New UnityContainer().AddNewExtension(Of EnterpriseLibraryCoreExtension)()

Note

If you choose this approach to using Enterprise Library in your applications, you should consider importing the namespaces Microsoft.Practices.EnterpriseLibrary.Common.Configuration.Unity and Microsoft.Practices.Unity into your code. These namespaces include the container and core extension definitions.

Dependency Injection Example

Typically, when you access the container directly to benefit from dependency injection, you will use it to resolve your own concrete types, and the container will inject Enterprise Library objects along with all other objects and dependencies. The following example shows a TaxCalculator type with two dependencies, ExceptionManager and LogWriter.

public class TaxCalculator 
{
  private ExceptionManager _exceptionManager;
  private LogWriter _logWriter;

  public TaxCalculator(ExceptionManager em, LogWriter lw) 
  {
    this._exceptionManager = em;
    this._logWriter = lw;
  }
}
'Usage
Public Class TaxCalculator

  Private _exceptionManager As ExceptionManager
  Private _logWriter As LogWriter

  Public Sub New(em As ExceptionManager, lw As LogWriter)
    Me._exceptionManager = em
    Me._logWriter = lw
  End Sub

End Class

Notice that the dependencies of this type are obvious from simply looking at the TaxCalculator constructor. It clearly shows that TaxCalculator is using the Exception Handling Application Block and the Logging Application Block.

When you have a reference to the container, you can use it to specifically resolve objects such as the TaxCalculator, as shown here.

TaxCalculator calc = container.Resolve<TaxCalculator>();
'Usage
Dim calc As TaxCalculator = container.Resolve(Of TaxCalculator)()

The container will automatically populate all the dependencies of the resolved instance. Alternatively, if the TaxCalculator class is defined as a dependency of another type, an ExceptionManager and LogWriter will be automatically created and injected into it when its parent type is resolved.

For information about when to store a reference to the container, and how you can do this in different types of applications, see Storing a Reference to the Container.

For information about the types of Enterprise Library objects that you can obtain through the container, see Non-Static Instances and Instance Factories.

For more information about how to resolve instances of Enterprise Library types and your own custom types in your applications, see Creating Application Block Objects.

Non-Static Instances and Instance Factories

All of the application blocks contain non-static instances and non-static factories that replace the mixture of static methods and instance factories used in versions of Enterprise Library prior to version 5.0. Some of the new non-static instances and factories are abstract classes that you cannot instantiate directly. However, you can obtain concrete configured instances of any of the following from the Enterprise Library container using both the service locator and through dependency injection, and then call methods on it that exercise the functionality of the block.

Application Block

Non-static Instance or Factory

Caching

ICacheManager

Cryptography

CryptographyManager

Data Access

Database

Exception Handling

ExceptionManager

Logging

LogWriter

TraceManager

Security

ISecurityCacheProvider

IAuthorizationProvider

Validation

ValidatorFactory

ConfigurationValidatorFactory

AttributeValidatorFactory

ValidationAttributeValidatorFactory

Non-Static Instances and Factories Examples

The following example uses the GetInstance method to obtain an ExceptionManager instance from the Exception Handling Application Block, and then calls one of its methods.

ExceptionManager em = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();
em.Process( () => MethodThatMightThrow(), "My Exception Policy");
'Usage
Dim em As ExceptionManager =  EnterpriseLibraryContainer.Current.GetInstance(Of ExceptionManager)() 
em.Process(AddressOf MethodThatMightThrow, "My Exception Policy")

The following example uses the GetInstance method to obtain a ValidatorFactory instance from the Validation Application Block, and then uses this to create an Object Validator that can validate the members of a Customer instance (not shown here).

ValidatorFactory factory = EnterpriseLibraryContainer.Current.GetInstance<ValidatorFactory>();
Validator<Customer> customerValidator = factory.CreateValidator<Customer>();

Customer myCustomer = new Customer("A name that is too long");
ValidationResults r = customerValidator.Validate(myCustomer);
if (!r.IsValid)
{
  throw new InvalidOperationException("Validation error found.");
}
'Usage
Dim factory As ValidatorFactory = EnterpriseLibraryContainer.Current.GetInstance(Of ValidatorFactory)()
Dim customerValidator As Validator(Of Customer) 
= factory.CreateValidator(Of Customer)()

Dim myCustomer As New Customer("A name that is too long")
Dim r As ValidationResults = customerValidator.Validate(myCustomer)
If Not r.IsValid Then
  Throw New InvalidOperationException("Validation error found.")
End If

Legacy Static Facades and Factories

Enterprise Library functionality has always been available through a set of static facades and factory classes. Some of these did the work directly, and some of them created other objects that did the work. However, these static facades and classes do not support use of Inversion of Control and Dependency Injection. The following table lists advantages and disadvantages of using the static facades and factories to instantiate objects:

Advantages

Disadvantages

Provides backwards compatibility. Code that used previous versions of Enterprise Library used this style and users of previous versions are quite familiar with the APIs.

Inconsistency in usage. Some blocks do the work directly in the helpers. Some use static factories and you have objects that do the actual work.

Provides simplicity for a block. Once you learn a block, it is fairly easy to call, as there is a small set of easily accessed methods to use.

Adds layer of difficulty to testing. Static methods make it difficult to write testable code. You cannot isolate your application from the Enterprise Library code, so you need to make sure there is a valid configuration available to your test project.

The legacy static facades and factories that were the default approach in versions of Enterprise Library prior to version 5.0 are still available, and continue to be supported for the purpose of backwards compatibility. However, new code should use either the service locator approach or the techniques for accessing the container directly, as described in previous sections of this topic.

Legacy Static Facades and Factories Examples

The following example logs a message from the logging block using a static Logger facade:

Logger.Write("My message");
'Usage
Logger.Write("My message")

However, if you were using the Data Access Application Block, you first need to get a Database object, which is acquired through the static DatabaseFactory. Then you call methods on the Database object, as in the following example:

Database db = DatabaseFactory.CreateDatabase("northwind");
db.ExecuteNonQuery("DoSomethingInteresting");
'Usage
Dim db As Database =  DatabaseFactory.CreateDatabase("northwind") 
db.ExecuteNonQuery("DoSomethingInteresting")

Each of the blocks has a set of legacy static facades or instance factory objects. In addition, some blocks contain objects that you can choose to construct directly using the new operator.

Initializing and Setting the Current Container

The EnterpriseLibraryContainer class wraps an instance of the Unity container by default, and exposes a reference to it through the Current property as an implementation of the IServiceLocator interface. Calls to the GetInstance method use this container to actually do their work.

You can specifically set up a Unity container by using a configurator. A configurator is responsible for obtaining the configuration information from a specific source and applying it to the container as a series of type registrations and mappings. This example uses the default Unity container configurator.

var container = new UnityContainer();
var configurator = new UnityContainerConfigurator(container);
// Read the configuration files and set up the container.
EnterpriseLibraryContainer.ConfigureContainer(configurator, ConfigurationSourceFactory.Create());

// The container is now ready to resolve Enterprise Library objects
'Usage
Dim container = New UnityContainer() 
Dim configurator = New UnityContainerConfigurator(container) 
' Read the configuration files and set up the container.
EnterpriseLibraryContainer.ConfigureContainer(configurator, ConfigurationSourceFactory.Create())
 
' The container is now ready to resolve Enterprise Library objects

If you are not using the dependency injection style in your application, or if you use the shortcut approach to initialize and populate the container described earlier in this topic, you generally do not care about this implementation detail. However, there may be occasions when you would like to change the underlying container used. The following are the two most common reasons to do this:

  • You have an application that is already using a dependency injection container for other things, but has older Enterprise Library client code in it as well, and you would like to integrate everything without rewriting all the calls to the static facades.
  • Your application uses static facades or EnterpriseLibraryContainer.Current.GetInstance to get Enterprise Library objects and you want to change the global configuration to use a source other than the application configuration file.

Changing the container is quite easy. Ultimately, all you need is an implementation of the IServiceLocator interface. However, if you want to load configuration information from a configuration source you can benefit from using an IConfigurationSource instance and a configurator, as well as an implementation of the IServiceLocator interface.

An object that implements the IConfigurationSource interface contains the Enterprise Library configuration information. Enterprise Library ships with several implementations that read this from the application configuration file, a separate file, and in an in-memory dictionary. This is used along with the configurator to set up the container.

The following code demonstrates how you can use the UnityContainerConfigurator configurator to set up the container, configure the container, and wrap the container with a ServiceLocator.

// Create the container
IUnityContainer container = new UnityContainer();

// Configurator will read Enterprise Library configuration 
// and set up the container
UnityContainerConfigurator configurator = new UnityContainerConfigurator(container);

// Configuration source holds the new configuration we want to use 
// load this in your own code
IConfigurationSource configSource = ReadConfigSource(); 

// Configure the container
EnterpriseLibraryContainer.ConfigureContainer(configurator, configSource);

// Wrap in ServiceLocator
IServiceLocator locator = new UnityServiceLocator(container);

// And set Enterprise Library to use it
EnterpriseLibraryContainer.Current = locator;
'Usage
' Create the container
Dim container As IUnityContainer = New UnityContainer() 
 
' Configurator will read Enterprise Library configuration and 
' set up the container
Dim configurator As UnityContainerConfigurator _
    = New UnityContainerConfigurator(container) 
 
' Configuration source holds the new configuration we want to use – 
' load this in your own code
Dim configSource As IConfigurationSource = ReadConfigSource() 
 
' Configure the container
EnterpriseLibraryContainer.ConfigureContainer(configurator, configSource)
 
' Wrap in ServiceLocator
Dim locator As IServiceLocator = New UnityServiceLocator(container)
 
' And set Enterprise Library to use it
EnterpriseLibraryContainer.Current = locator

Code for a different container would be similar, but would require container-specific variations on the configurator and locator wrapper classes.

If you are using the Enterprise Library default container implementation (Unity), you can achieve the same result by using the following code, where the default container refers to Unity. You need only to provide the source of the configuration because the method implements the required container setup steps for a new Unity container.

EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(ReadConfigSource());
'Usage
EnterpriseLibraryContainer.Current = EnterpriseLibraryContainer.CreateDefaultContainer(ReadConfigSource())

More Information

For more information about the techniques discussed in this topic, see the following:

There are many dependency injection containers and mechanisms available besides Unity. Enterprise Library can be used with almost any dependency injection container. This documentation specifically addresses the use of the Unity container. For information about using a different container, see The Dependency Injection Model.