Transferred from msdn
Http://www.microsoft.com/china/MSDN/library/enterprisedevelopment/EnterpriseLibrary.mspx? MFR = true use Enterprise Library to customize application blocks to accelerate development. Release Date: | updated on:
Mark seann
This article introduces the following:
• |
Write a new application block for the Enterprise Library |
• |
Build a plug-in loader application block |
• |
Understanding and implementing the factory |
• |
Implement custom application block configuration |
This article involves the following technologies:
• |
Enterprise Library, Visual Studio, C # |
You can download the code here:
Appblocks2006_07.exe (315kb)
Content on this page
|
Create application blocks |
|
Factory of the object |
|
Create a factory |
|
Define plug-in configuration |
|
Implement pluginprovider |
|
Design Behavior |
|
Provider Node |
|
Serialization and deserialization of XML |
|
Summary |
Enterprise Library for Microsoft. NET Framework 2.0 is an Application Block library, while application blocks are modular components designed to help developers cope with common development challenges. It provides a scalable framework for building robust and scalable applications. Although the Enterprise Library contains several useful application blocks, you can still integrate your own reusable components. In this article, I will demonstrate how to build a sample application block that is integrated with the Enterprise Library.
Microsoft's pattern and implementation solution team is the creator of the Enterprise Library and has created the term "Application Block" to describe libraries beyond the scope of DLL files. Although the application block is actually a DLL, it has a deeper unique feature.
Application blocks are configuration-driven, which means you can use configurations to define their behavior. This configuration can be defined in the standard. Net configuration file, or you can create a configuration at runtime and inject it into the Application Block. Some configurations are typical configuration values, such as the number of conditions used to define certain behaviors. However, in terms of the Application Block, the configuration can also be used to define the extension of the basic block.
Application blocks are still modular and scalable. Scalability is usually achieved through the Provider Model; a block delegates some work to a provider, and the provider implements a base class or interface used by the block. Although a block usually carries several pre-packaged providers (for example, providers that allow you to write log records in Windows event logs or providers that allow you to read and write SQL server data ), however, you can also define the provider type in the block configuration to develop and replace other providers.
When do you need to create an application block instead of a standard library? If you do not need to configure the library and do not want others to expand it, you do not need to create application blocks. However, if these functions may be useful, you should consider creating application blocks. In addition, because the Enterprise Library provides a large amount of configuration management infrastructure that can be used, you can benefit from creating application blocks, and creating an independent configurable library will not be able to enjoy these benefits.
Application blocks increase the complexity of libraries and the dependency on other blocks in the Enterprise Library. However, you obtain a public configuration management system and can use other parts of the Enterprise Library. For example, data access is a very common requirement. If you integrate an application block with the Enterprise Library, You can compile an optional provider for the block that uses the "Data Access Application Block. After everything is done correctly, this creates an optional dependency on the "Data Access Application Block". If you do not need data access, this dependency can be avoided.
Create application blocks
In this article, I will show you how to build an application block that can be used to manage application plug-ins. Please note that although the content I mentioned involves many aspects of Application Block Development, I will omit some content that is not applicable to Enterprise Library in the implementation section. Therefore, the solution I created in this article is simple and insecure. If you want to fully understand how to load a plug-in a secure and robust manner, see the Shawn Farkas msdnmagazine article "do you trust it? Explore technologies that use. NET Framework 2.0 to securely host untrusted external programs ". Since the plug-in loader application block I am creating here is extensible, therefore, you can develop a new provider that uses the methods described in the Shawn Farkas article and add them to this application block.
The above disclaimer may be skipped. Let's start. First, I will define the final goal of the Application Block. Because it aims to load plug-ins, I think a reasonable goal should be to enable developers to write the following code:
Pluginmanager Mgr = pluginfactory. getmanager (); ilist plugins = Mgr. getplugins (); // execute some work using the plug-in here...
Create a new pluginmanager that can be used to obtain the plug-in the static pluginfactory class. In this article, getplugins is the only member of pluginmanager. In a more comprehensive implementation, you can add other features (for example, enable or disable the capabilities of each plug-in at runtime ). Pluginmanager delegates the actual work to the provider specified in the configuration of the Application Block. Then, the provider loads and returns the plug-in according to the definition in its implementation.
The Application Block includes runtime components and optional design-time components. I will show you how to develop these two components. The library logic is implemented in the runtime component. If you want to use the application block, you need to reference the file in the project. The components involved in the development and runtime involve several steps.
A component is another library assembly that can be created and included during design. Just as the runtime component is integrated with the Enterprise Library runtime framework, the runtime component is also integrated with the design-time tool of the Enterprise Library. The main purpose of this function during design is to make it easier for developers to use application blocks. However, it can also be used by system administrators or other users to provide a graphical tool for editing application configurations.
An Application Block is a complex set of code that contains a large number of interactive classes. When you read this step-by-step guide, you may wonder why there must be so many unfixed parts. This is partly because the Enterprise Library provides great flexibility in how to implement application blocks. In many steps, Enterprise Library uses abstraction (such as interfaces and abstract classes) to complete certain tasks. Most of these solutions include an extensible default implementation, but it rarely forces you to use this default implementation. If you want to, you can create your own implementations.
Each time the Enterprise Library completes a task (for example, reading configuration data from persistent storage or instantiating a specific type when requesting an abstract type, you can expand the default behavior or derive from the default behavior. Each extension point in the Enterprise Library usually involves several interactive classes. Because the Enterprise Library contains a large number of extensions, there are also many interactive classes-you will soon find this on your own.
Back to Top
Factory of the object
The Enterprise Library uses the factory to create objects. It has its own attribute-based method. By using this method, you can use the factory type of the class to classify attributes, then, the factory is implemented by deriving from the base class provided by the Enterprise Library. Figure 1 briefly describes the typical object creation process. As you can see, this process is very complicated, but it must be remembered that it does not just create any type of instance. You request an abstract type and obtain the correct implementation of this type based on the configuration data. It has been configured and tested properly.
This object creation framework is highly scalable. You should use it as an object creation mechanism for your own application blocks, but you will soon find that you need to create a large number of factory classes. Figure 2 briefly describes the interaction between these classes. When plug-in loader is used, the static pluginfactory class is usually the entry point. This class is just a convenience class, which uses pluginmanagerfactory to create a pluginmanager instance. Most Enterprise Library application blocks provide such a static factory class as the entry point to the Application Block. When creating an Enterprise Library Application Block, you can choose whether to implement the static factory class, but I decided to include such a class for plug-in loader because it is very useful here.
Figure 2 interaction of the object factory class
Enterprise Library uses a factory-based approach to create configurable objects. Although the framework is flexible, it does not support generics because the types are unknown during compilation. For this reason, the generic classes of plug-in loader delegate most of their work to non-generic classes that can be created and managed by the Enterprise Library. Pluginmanager encapsulates the abstract pluginprovider class, while pluginmanagerfactory encapsulates pluginproviderfactory. If your application block does not support generics, you do not need to implement classes such as pluginmanager and pluginmanagerfactory.
Pluginproviderfactory creates the pluginprovider instance, which is derived from a class provided by the Enterprise Library. By using the customfactory attribute described in step 1 in Figure 1, pluginprovider identifies pluginprovidercustomfactory, and Enterprise Library combines pluginprovidercustomfactory with the configuration class to create the pluginprovider instance.
This seems complicated, but most of the work is executed by the Enterprise Library. You need to implement quite a few classes, but all the classes are very simple. You may ask yourself why it is so complicated. As I mentioned earlier, you do not just create an instance of pluginprovider. For beginners, you cannot instantiate pluginprovider because it is an abstract class. When you request the pluginprovider instance, the Application Block must provide the implementation of this abstract class based on the configuration data. Enterprise Library uses this mechanism to identify the content to be created and the creation method.
Back to Top
Create a factory
If you read the expected target code shown above, you can find that the class that I first need to implement is pluginmanager and pluginfactory. These two classes delegate most of their work to other classes. Pluginmanager uses pluginprovider to perform actual work, as shown in 3. Because the Enterprise Library object creation mechanism does not support generics, pluginprovider is not a generic class. Therefore, to return ilist, The ilist returned by pluginprovider must be converted to the corresponding type security set.
Pluginprovider itself is a simple class shown in 4. Since it is abstract, it provides an extension point for plug-in loader. If you want to extend the plug-in loader, You can derive a new class from pluginprovider and implement custom logic in the class. The most noteworthy pluginprovider feature is the customfactory attribute, which instructs the Enterprise Library how to create a new instance of the class derived from pluginprovider. In addition, pay attention to the abstract getplugins method, which must be implemented by the successor.
Pluginprovidercustomfactory is derived from the abstract class assemblerbased-customfactory provided by the Enterprise Library. When deriving from this class, the only task you must do is to implement the getconfiguration method. A new class: pluginloadersettings and pluginproviderdata appears for the first time, as shown in the following code:
public class PlugInProviderCustomFactory :AssemblerBasedCustomFactory{protected override PlugInProviderData GetConfiguration(string name, IConfigurationSource configurationSource){PlugInLoaderSettings settings =(PlugInLoaderSettings)configurationSource.GetSection(PlugInLoaderSettings.SectionName);return settings.PlugInProviders.Get(name);}}
These classes are configuration classes. I will discuss them in more detail in the following sections.
Now, the most important thing to note is that the getconfiguration method returns the corresponding configuration data so that the Enterprise Library can construct a new pluginprovider object, as shown in 1. After the custom factory is in place, I can create a factory class and then use the factory class to create a pluginprovider instance, as shown in the following code:
public class PlugInProviderFactory :NameTypeFactoryBase{public PlugInProviderFactory(IConfigurationSource configSource) :base(configSource) { }}
Although this is another factory class, all I need to do is to derive from the nametypefactorybase class and provide a public constructor. Pluginmanagerfactory only packs pluginproviderfactory. Pluginfactory creates and maintains a dictionary for these factories and delegates the work to the corresponding factory, as shown in code 5.
Note the naming conventions specific to plug-in loader. The Enterprise Library polymorphism set uses the names of the items they contain as keys, so each name must be unique in the set. For plug-in loader, the plug-in type should be the most intuitive key. However, this may require me to create my own collection classes (using types as keys), so I will not be able to reuse the classes provided by the Enterprise Library.
Since the component requires a unique name in all circumstances during design, the plug-in loader Convention applies to each pluginprovider that uses the name of a qualified assembly of the plug-in type for naming, the name of the qualified assembly is the name you see in Figure 5. This is not a good method, but the user will never notice it, because I will also process this convention in the components during design. On the other hand, if you like to edit the original XML, everything is just a string for you in any case.
This is the first step to create an Application Block. If you go back to Figure 1, you may wonder why I didn't define any assemblies. This is because the assembler depends on the implementation of pluginprovider, rather than the abstract pluginprovider class itself. Now I will introduce how to define the configuration class, and then I will introduce how to implement pluginprovider, where I will also discuss how to create the appropriate assemblies.
Back to Top
Define plug-in configuration
The configuration framework of the Enterprise Library is based on system. Configuration and works in a similar way. To define the configuration section of plug-in loader, I created the pluginloadersettings class, as shown in 6. The configuration section of the Application Block should be derived from Microsoft. Practices. enterpriselibrary. Common. configuration. serializableconfigurationsection, rather than directly derived from system. configuration. configurationsection. This allows you to add the Enterprise Library feature to this class. In addition, you can store the configuration section elsewhere in the application configuration file.
The pluginloadersettings class only contains a set of pluginproviderdata classes. The pluginproviderdata class contains the data used to configure the pluginprovider instance, as shown below:
public class PlugInProviderData :NameTypeConfigurationElement{public PlugInProviderData() :base() { }public PlugInProviderData(string name, Type type) :base(name, type) { }}
This class indicates a configuration element, and does not directly derive from system. configuration. configurationelement. If I want to create a simple configuration element, I can directly derive pluginproviderdata from configurationelement, but the Enterprise Library also provides me with two options. One option is the namedconfigurationelement class, and the other is the nametypeconfigurationelement. The former option adds a name to the configuration element, which is useful when implementing the design function of the Application Block. In addition, this name can be used as the unique key in the generic configuration set class provided by the Enterprise Library.
Nametypeconfigurationelement adds an additional type attribute to the configuration element, which can be used to support polymorphism sets, the polymorphism set is exactly what I need in this example-to specify different plug-in providers for different plug-in types (each with unique configuration settings ). Here, the element name is configured as the element key, and the type attribute identifies the type configured by the element. For plug-in loader, the type attribute identifier implements the pluginprovider type. Let's recall that, as agreed, plug-in loader uses the name attribute to store the qualified assembly name of the plug-in type. It is easy to confuse these two types, but the name identifies the plug-in type that the provider should provide services for, and the type attribute identifies the type of the provider. Because the name is a key, you only need to define the plug-in type once, but the same provider type can provide services for many different plug-in types. In fact, the most common situation is: they may all be provided by the same provider.
Given the method used by the Enterprise Library to construct these configuration element classes, the pluginproviderdata class cannot be abstracted, but you can regard it as abstract. Note that it does not actually perform any work, so in this special implementation method, I can omit it and create my configuration elements for different plug-in providers, the method is to directly derive them from nametypeconfigurationelement. However, the abstract pluginprovider class actually contains some implementations. If there is a one-to-one relationship between the provider and their configuration elements, it is easier to understand the structure of the Application Block Code.
Back to Top
Implement pluginprovider
So far, the abstract framework of the runtime component has been completed, but it still has no function. Now it's time to implement pluginprovider. This is a local implementation, so it is not secure and does not support plug-ins that can be detached from the memory. So I call it naivepluginprovider.
7. The naivepluginprovider class is derived from pluginprovider. Its main functions are implemented in the getplugins method. This method simply loads and reflects all types in all the assemblies in the configured folder. If a type implements the desired plug-in type, a new instance of this type is created and added to the returned plug-in list. Please note that this implementation requires that all plug-ins have default constructor, and more robust implementation can be improved.
Naivepluginprovider has two other features that are not particularly significant, but in the context of creating an Enterprise Library Application Block, these two features are very interesting: the configurationelementtype attribute is used, and the default constructor is missing.
When configuring the plug-in loader application block, you only need to care about which pluginprovider you want to use, rather than the class that provides configuration data for the provider. The configurationelementtype attribute contains this information, which means that the configuration data only contains information about which pluginprovider to create, the basic structure of Enterprise Library indicates which class contains the configuration data for the provider. In this example, this class is the naivepluginproviderdata class, as shown in 8. This class is derived from pluginproviderdata and provides an additional Configuration Attribute that allows you to specify a folder that contains the plug-in assembly.
Another interesting thing about naivepluginprovider is that it lacks the default constructor. If there is no default constructor, how does Enterprise Library create a new naivepluginprovider instance? Naivepluginproviderdata has a explorer attribute. This attribute identifies a type that can be used to create a naivepluginprovider instance from the naivepluginproviderdata instance.
The naivepluginproviderpolicer class is also displayed in figure 8. The Assemble Class must implement an icycler that contains a single assemble method. It uses the provided configuration data to retrieve relevant information and create a new naivepluginprovider instance.
Currently, plug-in loader contains a fully workable (though somewhat too simple) implementation that can be put into use. You can now continue and create more providers to create different plug-ins for the Application Block to discover the behavior. An obvious extension is a provider that follows security practices for discovering and loading plug-ins. Another possible extension is that a plug-in provider obtains the plug-in from blob in the SQL Server table, which may cause an optional dependency on the Data Access Application Block.
If you don't mind writing the entire configuration in the XML file manually, you can release your application block so far. Otherwise, you can create a design-time component to complete the work for you.
Back to Top
Design Behavior
The components are inserted into the Enterprise Library configuration application during design. This is an extensible windows form application that allows you to edit application configuration files on a wide array of user interfaces, rather than writing the original XML code. This component has three responsibilities: it must provide behavior for the application UI itself; it must allow the application to serialize user settings; when the user opens an existing application configuration file, it must be able to deserialize configuration data.
Because application configuration files are XML-based, they are inherently hierarchical. Configure the application to model this as a tree composed of nodes. Each configuration element in the runtime component must be represented by a design period vertex class, which provides additional design-time behavior for the configuration class. Figure 9 depicts the relationship between plug-in loader application blocks.
Figure 9 ing between runtime classes and design-time classes
To integrate with the Enterprise Library configuration application, components must be registered at design time. The Assembly and its dependencies must be in the same directory as the configuration application, and must be marked using the configurationdesignmanager attribute, as shown below:
[assembly:ConfigurationDesignManager(typeof(PlugInLoaderConfigurationDesignManager))]
This Assembly-level property registers pluginloaderconfigurationdesignmanager with the configuration application. This class is derived from the abstract configurationdesignmanager class.
Defining the work performed during design involves specifying specific actions that may be performed in a specific context. You can override the Register Method of configurationdesignmanager to achieve this goal:
Public override void register (iserviceprovider serviceprovider) {pluginloadercommandregistrar cmdregistrar = new pluginloadercommandregistrar (serviceprovider); cmdregistrar. Register (); // Add node ing code here ...}
Pluginloadercommandregistrar is derived from the abstract commandregistrar class, which aims to register design-time operations with the configuration application. The first operation is to add the application block to the application configuration file. When the plug-in loader application block is added to the application configuration, you must add pluginloadersettingsnode and its sub-pluginprovidercollectionnode to the hierarchy.
First, you must define these node classes, such:
public class PlugInLoaderSettingsNode :ConfigurationNode{public PlugInLoaderSettingsNode() :base("Plug-In Loader Application Block") {}[ReadOnly(true)]public override string Name{get { return base.Name; } set { base.Name = value; }}}
Pluginprovidercollectionnode is almost identical, because pluginloadersettingsnode does not contain attributes other than the pluginproviders set. Although you may think that I can use a public class for two nodes, this does not apply to this case. The two nodes occupy different positions in the hierarchy, and I will attach different operations to them. You may wonder why I overwrite the name property. In fact, I only use the read-only attribute to mark it. This will make these nodes read-only in the configuration application.
When you call the command to add the plug-in loader application block to the application configuration file, you must add the two nodes to the hierarchy. To achieve this, I created the addpluginloadersettingsnodecommand class, as shown in 10. It derives from addchildnodecommand and overwrites the executecore method to implement the desired logic. This command class must be associated with the node class so that the base class knows that it should create a pluginloadersettingsnode instance and add it to the hierarchy. This is achieved after the executecore base implementation is called, so all I need to do is create a new pluginprovidercollectionnode and add it to the set node.
The addpluginloadersettingsnodecommand class defines the operations that occur when a user calls this command. However, I still need to define the time and usage of this command. The command should be used only when the user selects the application to configure the root node, and the command should be called only once. I rewrite the abstract register method to implement this purpose in the pluginloadercommandregistrar class:
Public override void register () {This. addpluginloadercommand (); // Add other commands here ...}
The addpluginloadercommand method contains only three statements, as shown below:
private void AddPlugInLoaderCommand(){ConfigurationUICommand cmd =ConfigurationUICommand.CreateSingleUICommand(this.ServiceProvider, "Plug-In Loader Application Block","Add the Plug-In Loader Application Block",new AddPlugInLoaderSettingsNodeCommand(this.ServiceProvider),typeof(PlugInLoaderSettingsNode));this.AddUICommand(cmd, typeof(ConfigurationApplicationNode));this.AddDefaultCommands(typeof(PlugInLoaderSettingsNode));}
By calling createsingleuicommand, I specify that this command can be called only once. In this method call, I also provide an instance for displaying text and addpluginloadersettingsnodecommand. This instance is called when the user chooses to execute this operation. By calling adduicommand, I associate the command with the configurationapplicatonnode type, which is the type of the root node configured by the application. The adddefacommandcommands method adds the default commands (such as add and delete) to the newly created pluginloadersettingsnode.
Back to Top
Provider Node
Pluginproviderdata must be enhanced by pluginprovidernode, while naivepluginproviderdata is enhanced by naivepluginprovidernode, as shown in figure 9. The abstract pluginprovidernode in Figure 11 provides the design-time function for pluginproviderdata. Several attributes from the system. componentmodel namespace come in handy here: category, editor, and readonly. Their functions are the same as those in the Visual Studio attribute mesh.
Figure 9 ing between runtime classes and design-time classes
Pluginprovidernode encapsulates the pluginproviderdata instance, which provides and stores all configuration data except the plug-in type. Plug-in loader uses special naming conventions. According to this Convention, the pluginprovider name is a qualified assembly name of the plug-in type. Because the name attribute cannot be parsed as a type, pluginprovidernode stores the plug-in type separately in a member variable.
The plugintype property also contains the basetype (attribute). The typeselectoreditor identified in the editor (attribute) uses the basetype to filter available types. When the editor displays available types, only the abstract base types (or interfaces) are listed ). When you select a plug-in type, these are the only types worth listing, because you cannot build the plug-in on the basis of the seal type.
Another noteworthy pluginprovidernode function is the read-only provider attribute. I found that checking which provider is configured is always a good thing, and this provides the most direct way to inform users of the information. Otherwise, it is really difficult to tell the time to use the configuration application.
The last thing worth noting about pluginprovidernode is that I use onrenamed to save the node name and the name of the basic data to be synchronized.
The naivepluginprovidernode class extends the pluginprovidernode by providing the pluginfolder attribute. It is easier to add the naivepluginprovider command to configure the application than to add the Application Block itself, because this command does not need to create an additional subnode under naivepluginprovidernode. Therefore, I do not need to create a separate command class for this operation. On the contrary, I can have pluginloadercommandregistrar handle all command registration:
this.AddMultipleChildNodeCommand("Naive Plug-In Provider", "Add a new naive Plug-In Provider",typeof(NaivePlugInProviderNode),typeof(PlugInProviderCollectionNode));this.AddDefaultCommands(typeof(NaivePlugInProviderNode));
By calling addmultiplechildnodecommand, I specify that this command can be called multiple times to create a new naivepluginprovidernodes and act as a subnode of pluginprovidercollectionnode. You can also use a similar mechanism to add other pluginprovider node types.
Back to Top
Serialization and deserialization of XML
When you use the configuration application, the settings are only kept in memory before you choose to save the changes. After saving the changes, the data configured in each node must be serialized as XML. Fortunately, Enterprise Library and system. configuration are responsible for serialization and deserialization between configuration elements and XML. The only task you need to do is to specify how to map nodes to the configuration class.
You can override the getconfigurationsectioninfo method of pluginloaderconfigurationdesignmanager to achieve this goal. This implementation obtains pluginloadersettingsnode from the hierarchy, but delegates the actual work to the pluginloadersettingsbuilder class, as shown in 12. This internal class creates a new blank pluginloadersettings instance, and then adds the configuration data to the instance using the node hierarchy.
Since plug-in loader has a very low configuration hierarchy, you only need to cycle through all pluginproviders and add configuration data from their nodes. If the hierarchy is deep, this operation may need to traverse the node hierarchy and establish the equivalent hierarchy of the configuration class.
Just as Enterprise Library and system. configuration are responsible for serialization to XML, the framework also performs deserialization from XML to configuration class instances. However, you must provide code to map the configuration class to the node hierarchy. The first step is to override the opencore method of pluginloaderconfigurationdesignmanager:
protected override void OpenCore(IServiceProvider serviceProvider,ConfigurationApplicationNode rootNode, ConfigurationSection section){if (section != null){PlugInLoaderSettingsNodeBuilder builder =new PlugInLoaderSettingsNodeBuilder(serviceProvider,(PlugInLoaderSettings)section);rootNode.AddNode(builder.Build());}}
Similar to serialization into XML, here I delegate the actual work to another class: pluginloadersettingsnodebuilder. This class is derived from nodebuilder. nodebuilder provides some practical tools for configuring ing between classes and nodes. My idea is: the root node and all the collection nodes have the node builder class associated with it, so the node builder class only contains the code for creating its own node type, instead, the work of creating the hierarchy of the remaining nodes is delegated to other node builder classes. This applies to pluginloadersettingsnodebuilder, which creates a new pluginloadersettingsnode and delegates the work of creating the pluginproviders set to another class. The download code in this article shows the specific implementation method.
This Code uses nodecreationservice to create a new pluginprovidernode from the configuration data and add the node to the Collection node. To enable nodecreationservice to perform ing, pluginloaderconfigurationdesignmanager must register the node ing in the register method.
To create and register node ing, I create the pluginloadernodemapregistrar class (derived from nodemapregistrar) and override its abstract register method. Here, by calling the inherited addmultiplenodemap method, I simply set up a ing between the configuration data class and the corresponding node class:
this.AddMultipleNodeMap("Naive Plug-In Provider",typeof(NaivePlugInProviderNode), typeof(NaivePlugInProviderData));
By using the node builder class mechanism, I can build a node hierarchy with almost any depth and complexity, while keeping the implementation process of each node builder class relatively simple. This method seems too complex for the examples in this article, but it is very scalable and can be used in more complex configuration solutions.
Back to Top
Summary
The process of creating an Enterprise Library Application Block is not that easy, but it has proved to be a great value for money for the right project type. The core of creating an Enterprise Library Application Block (or converting a standard, configuration-driven library into an Application Block) is to develop runtime components. However, optional additional components are equally important. I introduced the components during design, but this is only the first step in several steps.
It is not difficult to package application blocks in Microsoft Installer (MSI) packages, making it easier to download and install application blocks. In addition, you should also consider creating a complete document-not only an API document generated from XML annotations, but also a quick introduction, Architecture Overview, Getting Started Guide, and so on.
Most developers learn through examples, so several excellent Quick Start sample programs will make the application block easier to adopt. Even if your audience is limited to within the organization, it is worth your effort.