Lab 8: Build your own Block
After completing this lab, you will be able to:
- Abstract some functionality into an external application.
- Support run-time configurable multiple providers.
Third party modification:
Scenario
WARNING: This is a code heavy lab.
Your reward for finishing this lab (or skipping to the end to
run the end solution) will be to have a Clippit™ like character
that knows when you are writing a letter.
This lab uses the String Resource Tool for localization,
which you have included under labs/setup
Please install this unless you have a newer or
Microsoft version. The newest version can always been found on
the Readify project distributor
Website.
In this lab, you will abstract some functionality into a new
application block. The steps to do this are:
[Exercise 1]
- Create an abstract client API and plugin API, using the
provider model
- Create configuration data for the provider factory and a
custom provider
- Create a provider factory derived from the Configuration
Block ProviderFactory class
- Create a concrete provider by implementing the plugin API.
- Create design time configuration assembly for the provider
factory configuration data and custom provider.
[Exercise 2]
- Create configuration data for the included providers.
- Create design time configuration classes for the included
providers.
[Exercise 3]
- Implement external providers in a separate assembly.
Other steps that would typically occur, which you won't cover in
this lab:
- Build unit tests for the providers in the block.
- Document all the classes in the block (and integrate with
VS.NET).
Estimated Time To Complete this lab: 60 minutes.
Exercise 1: Review Current Application
In this exercise, you will investigate the
piece of re-usable functionality that you want to turn into an
application block.
First step
- Open the
Ex01.sln file, and build the solution.
Run the application
- Run the application, and type
the following (do not copy & paste):
dear sir,
thank you for your interest in patterns, you hope you
enjoy Enterprise Library.
yours sincerely,
p&p team
See the file SmartWords.txt in the
EditorApplication project for a list of the phrases
detected.
- As you can see, some phrases are
automatically matched, and associated text is displayed in the
status bar at the bottom.
Review the Application code
- Close the application.
- Open the file EditorForm.cs, right-click
and select View Code.
- Find the method
DisplayNotification. You will change the method by
abstracting out the notification to the user, and make it
configurable at deployment.
- Close Visual Studio.
Exercise 2: Create a Notification Block
Exercise 2: Create a
Notification Block in wizard format
This exercise will create the notification
application block, and support adding external providers.
First step
- Open the
Ex02.sln file, and build the solution.
Create the abstract API
- You have created a new project, called
Notification (FourthCoffee.Framework.Notification.dll), which
will contain the API for our application block, as well as a
single provider. You have created skeleton files for all the
files you need to implement.
- Open the file INotificationProvider.cs
in the Notification project. Add the following
interface within the namespace. This will be our provider
interface.
/// <summary>
/// Interface exposed by any provider that supports
notification
/// </summary>
public interface INotificationProvider :
IConfigurationProvider
{
/// <summary>
/// Display a message to the user.
/// This method must not block execution, so if the
message display
/// will take any length of time, it should occur
asynchronously.
/// </summary>
/// <param name="parent">The parent form to use when
displaying the message</param>
/// <param name="message">The message to display to the
user</param>
void DisplayMessage(Form parent, string message);
}
Note that the provider interface derives
from IConfigurationProvider. This ensures that any
implementation of the provider can be initialized through
configuration. It also means, however, that all clients of
this API must also add a reference to the Configuration
assembly.
Create the configuration data for the block
- Within the Configuration
folder, open the NotificationSettings.cs file. Add the following
class within the namespace definition:
public const string SectionName =
"notificationConfiguration";
}
The XmlRoot attribute is important to
specify the namespace of the XML as it appears when
serialized, required to make the XML unambiguous. You will
reference these two public constants throughout the rest of
the code.
- Add the following properties and backing
fields to the NotificationSettings class:
private NotificationProviderDataCollection
_notificationProviders = new
NotificationProviderDataCollection();
private string _defaultNotificationProviderName = null;
[XmlArray(ElementName="notificationProviders")]
[XmlArrayItem(ElementName="notificationProvider",
Type=typeof(NotificationProviderData))]
public NotificationProviderDataCollection
NotificationProviders
{
get { return _notificationProviders; }
}
[XmlAttribute("defaultProviderName")]
public string DefaultNotificationProviderName
{
get { return _defaultNotificationProviderName; }
set { _defaultNotificationProviderName = value; }
}
The NotificationProviders property
provides the data for the configured notification
providers. Note that the type of the property should
derive from ProviderDataCollection (defined in the
Configuration assembly), to support some of the design
time functionality (as you shall show later).
The DefaultNotificationProviderName
property will be used to allow the client of the block
to not specify a provider name, and use a default
specified in configuration.
The Xml* attributes are being used to customize
the saved XML.
-
Open the NotificationProviderData.cs file, and implement the
NotificationProviderData class. This will
contain the base class that all the
configuration for the providers will derive from. This class
is used for two purposes:
- To contain any common configuration data that all
providers use. In this case you do not have any common
configuration settings for all our providers (apart from
Name and Type, which are defined in the ProviderData
base class).
- This is also the location where you can add static
XmlInclude attributes that will allow the XmlSerializer to
de-serialize derived classes. Of course, you can only add
XmlInclude attributes for classes in this assembly. You will
see in the next exercise how to include additional classes
for externally defined providers.
[XmlInclude(typeof(CustomNotificationData))]
public abstract class NotificationProviderData :
ProviderData
{
}
- Open the CustomNotificationData.cs file,
and implement the CustomNotificationData class.
This class is responsible for holding the type information and
supporting additional weakly typed configuration data for
providers which do not have design time support.
[XmlRoot("notificationProvider",
Namespace=NotificationSettings.ConfigurationNamespace)]
public class CustomNotificationData :
NotificationProviderData
{
private string _typeName;
private NameValueItemCollection _attributes = new
NameValueItemCollection();
[XmlAttribute("type")]
public override string TypeName
{
get { return _typeName; }
set { _typeName = value; }
}
[XmlElement("Attributes")]
public NameValueItemCollection Attributes
{
get { return this._attributes; }
}
}
NOTE: It is very
important that the XmlRoot attribute exists, and specifies a
namespace. If a namespace is not specified, then the
configuration data cannot be de-serialized by the
XmlSerializer (as the de-serializer will be looking for a
blank namespace, when the XML namespace will actually be set
by the containing XML elements).
- Open
NotificationProviderDataCollection.cs. Implement the
provider collection class:
public class NotificationProviderDataCollection :
ProviderDataCollection
{
public NotificationProviderData this[int index]
{
get { return (NotificationProviderData)
GetProvider(index); }
set { SetProvider(index, value); }
}
public NotificationProviderData this[string name]
{
get { return (NotificationProviderData)
GetProvider(name); }
set { SetProvider(name, value); }
}
public void Add(NotificationProviderData
providerData)
{
AddProvider(providerData);
}
}
This class is used by the Settings class
to maintain the list of provider data. It should derive from
the ProviderDataCollection class (rather
than, for example, CollectionBase), because the
configuration design time support can provide additional
functionality with ProviderDataCollection
based classes.
Create the configuration view
- The configuration view class is
responsible for querying the configuration subsystem for the
current configuration information. By using this class, the
provider will automatically get the latest configuration
information, even if the configuration has changed on disk while
the application is running.
- Open the
NotificationConfigurationView.csfile. Implement the
NotificationConfigurationView class with the following
code:
public class NotificationConfigurationView
: ConfigurationView
{
public
NotificationConfigurationView(ConfigurationContext
context) : base(context)
{}
public virtual NotificationSettings
GetNotificationSettings()
{
return (NotificationSettings)
ConfigurationContext.GetConfiguration(NotificationSettings.SectionName);
}
public virtual NotificationProviderData
GetNotificationProviderData(string providerName)
{
ArgumentValidation.CheckForNullReference(providerName,
"sinkName");
ArgumentValidation.CheckForEmptyString(providerName,
"sinkName");
NotificationSettings settings =
GetNotificationSettings();
NotificationProviderData sinkData =
settings.NotificationProviders[providerName];
if (null == sinkData)
{
throw new
ConfigurationException(SR.NoSuchProviderDefined(providerName));
}
return sinkData;
}
}
The ConfigurationContext
class will load and cache the configuration section
defined in the application configuration. It will also
automatically re-load the configuration on demand if it is
changed in the source (e.g. on disk).
Create the notification provider factory
- OpenNotificationProviderFactory.cs, and
add the following class:
public class NotificationProviderFactory :
ProviderFactory
{
public NotificationProviderFactory() :
this(ConfigurationManager.GetCurrentContext())
{}
public
NotificationProviderFactory(ConfigurationContext
configurationContext) :
base(SR.HandlerFactoryName,
configurationContext, typeof(INotificationProvider))
{}
private NotificationConfigurationView
GetConfigurationView()
{
return new
NotificationConfigurationView(this.ConfigurationContext);
}
// TODO: Implement protected overloads to
provide type information
// TODO: Implement strongly-typed creation methods
}
- Add the following methods, which are
required for the base class factory
implementation to work:
protected override ConfigurationView
CreateConfigurationView()
{
return GetConfigurationView();
}
protected override string GetDefaultInstanceName()
{
return
GetConfigurationView().GetNotificationSettings().DefaultNotificationProviderName;
}
protected override Type GetConfigurationType(string
configurationName)
{
string typeName =
GetConfigurationView().GetNotificationProviderData(configurationName).TypeName;
return base.GetType(typeName);
}
- Add the following methods, which provide
strongly typed creation of the providers:
public INotificationProvider GetNotificationProvider()
{
return (INotificationProvider)
base.CreateDefaultInstance();
}
public INotificationProvider
GetNotificationProvider(string providerName)
{
return (INotificationProvider)
base.CreateInstance(providerName);
}
Create a static Factory class for
one-line creation of providers
- Open the NotificationFactory.cs file,
and create the following NotificationFactory
class, which provides static helper methods to create an
instance of the notification provider (default provider and
named provider):
public sealed class NotificationFactory
{
private NotificationFactory()
{
}
public static INotificationProvider
GetNotificationProvider()
{
NotificationProviderFactory factory = new
NotificationProviderFactory();
return factory.GetNotificationProvider();
}
public static INotificationProvider
GetNotificationProvider(string providerName)
{
NotificationProviderFactory factory = new
NotificationProviderFactory();
return
factory.GetNotificationProvider(providerName);
}
}
Implement a weakly typed provider
- Open StatusBarNotificationProvider.cs.
Within the file, you have partially implemented the Status Bar
Notification Provider. This provider will search for a status
bar in the form which is asking for notification (or its
parent), and then display the message for a certain length of
time in the status bar.
- Derive the class from
ConfgurationProvider, and implement the
INotificationProvider interface (changed code in bold):
public class StatusBarNotificationProvider
: ConfigurationProvider,
INotificationProvider
The ConfigurationProvider
base class implements the
IConfigurationProvider interface, and manages the
Configuration Name of the provider. This class must then
override the Initialize method of the
ConfigurationProvider to be able to obtain configuration
data later.
- Implement the Initialize
method with the following code:
NotificationConfigurationView config = null;
public override void Initialize(ConfigurationView
configurationView)
{
ArgumentValidation.CheckExpectedType(configurationView,
typeof(NotificationConfigurationView));
config = (NotificationConfigurationView)
configurationView;
}
- Implement the DisplayMessage
method (from INotificationProvider) with the following code:
public void DisplayMessage(Form parent, string message)
{
// Default display timeout
int displayTimeout = 2000;
CustomNotificationData configData =
(CustomNotificationData)config.GetNotificationProviderData(ConfigurationName);
if
(configData.Attributes.GetNameValueItem("DisplayTimeout")
!= null)
{
displayTimeout =
int.Parse(configData.Attributes.GetNameValueItem("DisplayTimeout").Value);
}
StatusBar bar = SearchForStatusBar(parent);
StatusBarManager manager = new
StatusBarManager(bar);
manager.Display(message, displayTimeout);
}
This code obtains the current display
timeout value using the Custom notification configuration
data, if set, finds the status bar, and then uses the
StatusBarManager class to display the
message.
Add design time support
- You have now implemented all the
run-time provider infrastructure that you need. You now need to
create the design-time support for the configuration console.
Build the solution and fix any compile errors at this point.
- Open NotificationProviderNode.cs in the
Notification.Configuration.Design project.
Implement the Notification Provider Node class,
which is the design time representation of the
NotificationProviderData class:
[Image(typeof(NotificationProviderNode))]
public abstract class NotificationProviderNode
: ConfigurationNode
{
[Browsable(false)]
public abstract NotificationProviderData
NotificationProviderData
{
get;
}
protected override void OnSited()
{
base.OnSited();
Site.Name = NotificationProviderData.Name;
}
protected override void
OnRenamed(ConfigurationNodeChangedEventArgs e)
{
base.OnRenamed (e);
NotificationProviderData.Name = e.Node.Name;
}
}
This class is abstract, as each of the provider types
(at this point there is only the Custom Notification
Provider), will have their own concrete xyzNode
class that is used to manipulate their data.
The Image tag is used to provide the bitmap that will be
displayed in the tree. In this case, a bitmap named
NotificationProviderNode.bmp should be in the project as
an embedded resource.
The OnSited and OnRenamed
methods are use to pull and push the Name information
from/to the underlying NotificationProviderData
instance (which is provided by the concrete
xyzNode class).
- Open the
CustomNotificationProviderNode.cs file, and implement the
CustomNotificationProviderNode class, which is
used to manipulate the CustomNotificationData
configuration class.
public class CustomNotificationProviderNode :
NotificationProviderNode
{
private CustomNotificationData
customNotificationData;
public override NotificationProviderData
NotificationProviderData
{
get
{
return customNotificationData;
}
}
// Initializes node with default data.
public CustomNotificationProviderNode()
{
this.customNotificationData = new
CustomNotificationData();
this.customNotificationData.Name =
SR.DefaultCustomNotificationProviderNodeName;
}
// Initializes node with loaded data.
public
CustomNotificationProviderNode(CustomNotificationData
customNotificationData)
{
this.customNotificationData =
customNotificationData;
}
// TODO: Add properties that provide design-time
access to configuration class
}
This class is the design-time access
class for the custom notification provider's configuration
data.
- Add the Attributes property to the
CustomNotificationProviderNode class:
[SRDescription(SR.Keys.NotificationProviderAdditionalPropertiesDescription)]
[SRCategory(SR.Keys.CategoryGeneral)]
public NameValueItemCollection Attributes
{
get { return customNotificationData.Attributes;
}
}
The SRDescription and
SRCategory attributes are attribute classes
that use resources to specify the Description and
Category at runtime of the particular property in the
property grid. This is used to support localization.
The SR class is code-generated from the
SR.strings file, which is also located in the same
project. The Custom Tool that is used to generate the
strongly-typed resource class can be
downloaded from the link at the top of this lab.
- Add the Type Property to the Same Class:
[Required]
[Editor(typeof(TypeSelectorEditor),
typeof(UITypeEditor))]
[BaseType(typeof(INotificationProvider))]
[SRDescription(SR.Keys.NotificationProviderTypeDescription)]
[SRCategory(SR.Keys.CategoryGeneral)]
public virtual string TypeName
{
get { return customNotificationData.TypeName; }
set { customNotificationData.TypeName = value; }
}
This property uses the EditorAttribute
(from System.ComponentModel) to specify the editor to
use when displaying this property in the property grid.
In this case, the editor is the Enterprise Library type
selection dialog, which uses the BaseType
attribute to filter the types available.
The Required attribute (from the
Configuration block) is used by Configuration Block
validation to ensure that the property has been set
before the user can save the configuration in the
console.
- Open the NotificationSettingsNode.cs,
and implement the NotificationSettingsNode
class, which represents the root node of the Notification Block:
[Image(typeof (NotificationSettingsNode))]
public class NotificationSettingsNode :
ConfigurationNode
{
private NotificationSettings notificationSettings;
public NotificationSettingsNode() : this(new
NotificationSettings())
{}
public NotificationSettingsNode(NotificationSettings
notificationSettings)
{
this.notificationSettings =
notificationSettings;
}
/// <summary>
/// The configured name.
/// </summary>
[ReadOnly(true)]
public override string Name
{
get
{
return base.Name;
}
set
{
base.Name = value;
}
}
/// <summary>
/// Retrieves configuration data based on the
current state of the node.
/// </summary>
/// <returns>Configuration data for this
node.</returns>
[Browsable(false)]
public virtual NotificationSettings
NotificationSettings
{
get
{
notificationSettings.NotificationProviders.Clear();
foreach (NotificationProviderNode node in
this.Nodes)
{
notificationSettings.NotificationProviders.Add(node.NotificationProviderData);
}
return notificationSettings;
}
}
}
- During the life-cycle of a node in the
configuration console, it has a few chances to change the host
environment. When this node is added to the console (e.g. when
an application configuration file is loaded), you need to add
all the nodes for each of the notification providers configured.
Similarly, when given the chance to add menu items, you want to
add a menu item under this node to create new notification
providers. Add the following code to the
NotificationSettingsNode class:
protected override void OnSited()
{
base.OnSited ();
Site.Name = SR.DefaultNotificationSettingsNodeName;
CreateDynamicNodes(notificationSettings.NotificationProviders);
}
protected override void OnAddMenuItems()
{
base.OnAddMenuItems ();
CreateDynamicMenuItems(typeof(NotificationProviderNode));
}
The CreateDynamicNodes call will
iterate through the NotificationProviders collection,
and create nodes of the appropriate type based on the
type of the instance of the object in that collection.
The mapping from configuration data type to node data
type is performed within a ConfigurationDesignManager
(as you will do later).
CreateDynamicMenuItems, similarly, will
iterate through any registered node types that are
derived from the given type, and create menu items for
them.
-
You need to add a property to this class to allow the user
to select a default notification provider. Once
selected, you need to ensure that the name of the provider
stored within the NotificationSettings
class always matches the name of the provider within its own
NotificationProviderData class. You also
need to take care of the case where the user selects a
provider as the default, and then deletes it. Unfortunately,
there is no succinct way of doing this, and so the following
code is quite complex. In future versions of Enterprise
Library, there may be a shortcut way of implementing it.
Add the following code to the
NotificationSettingsNode class:
NotificationProviderNode defaultNotificationProviderNode
= null;
[Editor(typeof(ReferenceEditor), typeof(UITypeEditor))]
[ReferenceType(typeof(NotificationProviderNode))]
[SRCategory(SR.Keys.CategoryGeneral)]
[SRDescription(SR.Keys.DefaultProviderDescription)]
public NotificationProviderNode
DefaultNotificationInstance
{
get { return defaultNotificationProviderNode; }
set
{
ILinkNodeService service =
GetService(typeof(ILinkNodeService)) as
ILinkNodeService;
Debug.Assert(service != null, "Could not get the
ILinkNodeService");
defaultNotificationProviderNode =
(NotificationProviderNode)service.CreateReference(
defaultNotificationProviderNode,
value,
new
ConfigurationNodeChangedEventHandler(OnNotificationDefaultProviderRemoved),
new
ConfigurationNodeChangedEventHandler(OnNotificationDefaultProviderRenamed));
this.notificationSettings.DefaultNotificationProviderName
= (defaultNotificationProviderNode != null) ?
this.defaultNotificationProviderNode.Name :
string.Empty;
}
}
private void OnNotificationDefaultProviderRenamed(object
sender, ConfigurationNodeChangedEventArgs args)
{
this.notificationSettings.DefaultNotificationProviderName
= (defaultNotificationProviderNode != null) ?
this.defaultNotificationProviderNode.Name :
string.Empty;
}
private void OnNotificationDefaultProviderRemoved(object
sender, ConfigurationNodeChangedEventArgs args)
{
this.defaultNotificationProviderNode = null;
}
private void ResolveDefaultNotificationNode()
{
if
((notificationSettings.DefaultNotificationProviderName
== null) ||
(notificationSettings.DefaultNotificationProviderName.Length
== 0)) return;
DefaultNotificationInstance =
Hierarchy.FindNodeByName(this,
notificationSettings.DefaultNotificationProviderName) as
NotificationProviderNode;
}
public override void ResolveNodeReferences()
{
base.ResolveNodeReferences ();
ResolveDefaultNotificationNode();
}
The Editor attribute will create a
drop-down, which is populated with node instances which
derive from NotificationProviderNode (specified in the
ReferenceType attribute).
-
The last class you need to implement is called a
Configuration Design Manager. It is responsible for loading
the configuration into the designer, registering node types
and XML includes, and saving the configuration.
Open NotificationConfigurationDesignManager.cs, and
un-comment the
NotificationConfigurationDesignManager class.
The CreateCommands method is
responsible for adding the new notification section menu
item to the root node of the application in the console.
The RegisterNodeTypes method is
responsible for registering settings and provider node
types, which allow the dynamic menu item creation to
work.
-
Finally, in order for the configuration console to use the
Configuration Design Manager you created, you need to put
the assembly in the same directory as the configuration
console, and use an assembly attribute to provide the type
of the design manager.
You have already changed the build directory to be the
Enterprise Library bin directory (you can check in the
project properties window).
Open the AssemblyInfo.cs file in the
Notification.Configuration.Design project, and add
the following attribute at the bottom:
[assembly :
ConfigurationDesignManager(typeof(NotificationConfigurationDesignManager))]
Run the configuration console
- You have now completed the framework for
the new application block. You now have an application block
that will appear within the configuration console, supports
run-time configuration re-loads, and that you can extend with
additional providers at run-time.
- Build the solution, and ensure that it
built successfully.
- Run the Enterprise Library Configuration
tool. From the Windows Start menu select
All Programs | Microsoft patterns and practices |
Enterprise Library | Enterprise Library Configuration.
- Select File | Open Application
and browse to the App.config file located
here [ex02\begin\EditorApplication].
- Right-click over the Application node,
and add a new Notification section.
- Add a new custom notification provider.
- Click on the TypeName property. Click
the elipsis, and select the type
StatusBarNotificationProvider.
- Click on the Attributes property, and
add a new attribute:
Name: DisplayTimeout
Value: 3000
- Select the Notification section, and set
the default provider to Custom Notification Provider.
- Select the File | Save All
menu command to save the configuration.
Change the client application
-
You have already created post-build steps, and added the
required references and using statements.
Within the EditorApplication project,
right-click over the EditorForm.cs file and select
View Code.
Change the DisplayNotification method to
(modified code in bold):
private void DisplayNotification(string
description)
{
try
{
INotificationProvider provider =
NotificationFactory.GetNotificationProvider();
provider.DisplayMessage(this, description);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
Run the application
- Run the application, and type
the following (do not copy & paste):
dear sir,
thank you for your interest in patterns, you hope you
enjoy Enterprise Library.
yours sincerely,
p&p team
See the file SmartWords.txt in the
EditorApplication project for a list of the phrases
detected.
- Close the Configuration console, if it
is running. Re-open it, but this time open the application
configuration file from the
ex02\begin\EditorApplication\bin\Debug directory
(here). Try changing the DisplayTimeout attribute value in the
configuration console while the application is running. It may
take up to 15 seconds for a change to take effect.
To check the finished solution
labs\cs\Build Your Own Block\exercises\ex02\end\Ex02.sln
Exercise 3: Add strongly typed configuration data
In this exercise, you will change the status
bar provider to use strongly typed configuration data.
First step
- Open the labs\exercises\ex03\begin\Ex03.sln file, and build the solution.
Add run-time configuration
-
Open the new file StatusBarNotificationData.cs, which is in
the Configuration folder of the
Notification project.
Implement the StatusBarNotificationData
class with the following code:
[XmlRoot("notificationProvider", Namespace=NotificationSettings.ConfigurationNamespace)]
public class StatusBarNotificationData :
NotificationProviderData
{
[XmlIgnore]
public override string TypeName
{
get { return
typeof(StatusBarNotificationProvider).AssemblyQualifiedName;
}
set { }
}
int _displayTimeout = 2000;
[XmlElement("displayTimeout")]
public int DisplayTimeout
{
get { return _displayTimeout; }
set { _displayTimeout = value; }
}
}
The XmlRoot attribute is required to ensure that the
namespace of the class is set, for the same reasons as
the CustomNotificationData above.
The XmlIgnore attribute is used because the type of the
provider does not need to be serialized, as it will
always be the same. (The TypeName property is used at
runtime by the notification provider factory to specify
the concrete type of the provider).
The DisplayTimeout property will contain the timeout in
ms, with a default of 2000 ms.
- Open the file
NotificationProviderData.cs, and add the following
XmlInclude attribute before the class definition
(inserted code in bold):
[XmlInclude(typeof(CustomNotificationData))]
[XmlInclude(typeof(StatusBarNotificationData))]
public abstract class NotificationProviderData :
ProviderData
{
}
This is used to allow the Xml Serializer
infrastructure to de-serialize the
StatusBarNotificationData class when de-serializing
the NotificationProvideDataCollection instance.
- Open the file
StatusBarNotificationProvider.cs, and change the
DisplayMessage method as below (modified code in bold):
public void DisplayMessage(Form parent, string message)
{
StatusBarNotificationData configData =
(StatusBarNotificationData)config.GetNotificationProviderData(ConfigurationName);
int displayTimeout =
configData.DisplayTimeout;
StatusBar bar = SearchForStatusBar(parent);
StatusBarManager manager = new
StatusBarManager(bar);
manager.Display(message, displayTimeout);
}
Add design time configuration support
- Within the
Notification.Configuration.Design project, open the new
file StatusBarNotificationNode.cs, and
implement the StatusBarNotificationNode class:
public class StatusBarNotificationNode :
NotificationProviderNode
{
private StatusBarNotificationData
statusBarNotificationData;
public override NotificationProviderData
NotificationProviderData
{
get
{
return statusBarNotificationData;
}
}
public StatusBarNotificationNode()
{
this.statusBarNotificationData = new
StatusBarNotificationData();
this.statusBarNotificationData.Name =
SR.DefaultSatusBarNotificationNodeName;
}
public
StatusBarNotificationNode(StatusBarNotificationData
statusBarNotificationData)
{
this.statusBarNotificationData =
statusBarNotificationData;
}
[SRCategory(SR.Keys.CategoryBehavior)]
[SRDescription(SR.Keys.StatusBarTimoutDescription)]
public int DisplayTimeout
{
get { return
statusBarNotificationData.DisplayTimeout;}
set { statusBarNotificationData.DisplayTimeout =
value; }
}
}
In order to implement the design time support, the
Provider Node class must:
- Supply the configuration data instance that contains
the Name to its base class
- Provide constructors for a new instance (adding a
new provider), and for loading from a configuration
file.
- Provide properties for each of the data properties
that should be exposed, along with optional design time
attributes.
- Open the file
NotificationConfigurationDesignManager.cs, and add the following
code at the end of the RegisterNodeTypes method
(inserted code in bold).
private static void RegisterNodeTypes(IServiceProvider
serviceProvider)
{
INodeCreationService nodeCreationService =
ServiceHelper.GetNodeCreationService(serviceProvider);
Type nodeType =
typeof(CustomNotificationProviderNode);
NodeCreationEntry entry =
NodeCreationEntry.CreateNodeCreationEntryWithMultiples(new
AddChildNodeCommand(serviceProvider, nodeType),
nodeType, typeof(CustomNotificationData),
SR.DefaultCustomNotificationProviderNodeName);
nodeCreationService.AddNodeCreationEntry(entry);
nodeType =
typeof(StatusBarNotificationNode);
entry =
NodeCreationEntry.CreateNodeCreationEntryWithMultiples(new
AddChildNodeCommand(serviceProvider, nodeType),
nodeType, typeof(StatusBarNotificationData),
SR.DefaultSatusBarNotificationNodeName);
nodeCreationService.AddNodeCreationEntry(entry);
}
This registers the new node (and
associated data class) with the configuration console.
Build and Configure
- Build the application, and check that
there are no errors.
- Run the Enterprise Library Configuration
tool. From the Windows Start menu select
All Programs | Microsoft patterns and practices |
Enterprise Library | Enterprise Library Configuration.
- Select File | Open Application
and browse to the App.configfile located
here.
- There is already a notification section
configured. Add a new Status Bar Notification.
- Set the DisplayTimeout to 3000.
- Select the Notification section, and set
the default provider to Status Bar Notification.
- Select the File | Save All
menu command to save the configuration.
- Run the application, and test some the
smart words.
To check the finished solution
labs/cs/Build%20Your%20Own%20Block/exercises/ex03/end/Ex03.sln
Exercise 4: Add external providers
In this exercise you will add two providers for
notification, one based on MS Agent, and the other using a scrolling
title bar to display information.
First step
- Open the labs\exercises\ex04\begin\Ex04.sln Ex04.sln file, and build the solution.
Review the extended providers
-
In the Notification.Extended project are
two new providers, one to scroll the message in the forms
title bar, the other uses the Microsoft Agent technology to
display the messages.
- Open the
FormTitleNotificationNode class in the
Notification.Extended.Configuration.Design project.
This class demonstrates using the
validation attributes that are part of the Enterprise
Library configuration validation namespace (AssertRange),
as well as showing mapping between data values in the
runtime configuration, and design-time views.
- Open the AgentNotificationNode
class.
This class demonstrates using the
FilteredFileNameEditor which allows
browsing for a filename in the property grid, as well as the
FileValidation attribute which will check
that the file exists.
View the registration code for the external
providers
-
The one difference when creating providers in external
assemblies, compared to what you did with the status bar
notification provider, is managing the XML serialization.
With providers defined in external assemblies, it is
necessary to tell the configuration console to include those
types used within the configuration for XML Serialization.
This is done in the Configuration Design Manager.
Open the file ConfigurationDesignManager.cs within the
Notification.Extended.Configuration.Design
project.
The following code in RegisterXmlIncludeTypes
method registers the types for Xml inclusion:
private static void
RegisterXmlIncludeTypes(IServiceProvider
serviceProvider)
{
IXmlIncludeTypeService xmlIncludeTypeService
=
serviceProvider.GetService(typeof(IXmlIncludeTypeService))
as IXmlIncludeTypeService;
xmlIncludeTypeService.AddXmlIncludeType(NotificationSettings.SectionName,
typeof(FormTitleNotificationData));
xmlIncludeTypeService.AddXmlIncludeType(NotificationSettings.SectionName,
typeof(AgentNotificationData));
}
Configure the application
- Run the Enterprise Library Configuration
tool. From the Windows Start menu select
All Programs | Microsoft patterns and practices |
Enterprise Library | Enterprise Library Configuration.
- Select File | Open Application
and browse to the App.configfile located
here [ex04\begin\EditorApplication].
- Create a new notification provider for
each of the types, Agent Notification & Form Title Notification.
- Set the default notification provider to
be the Agent provider.
- Close the Configuration Console.
Add a reference to the extended notification
assembly from the application
- Add a project reference to the
EditorApplication by Project | Add Reference,
and selecting the Notification.Extended
project.
Run the application
- Build and Run the application. Test that
the Agent appears when typing in any of the magic phrases.
- Run the enterprise console, and this
time open the application configuration file from the runtime
directory of the application
-
Change the default provider, save, wait up to fifteen
seconds, and then type some more magic words (see
SmartWords.txt in the EditorApplication project for the
list). You will see the new provider setting used. You can
even change the agent that is being used on the fly, by
selecting a new agent file.
If you have Microsoft office installed, you can even use
Clippit, which can be found in a directory like <Program
Files>\Microsoft Office\OFFICE11.
For more information
- For further information on the
application of best practises, enterprise library and building
enterprise scale applications, see the
Mastering Industrial Strength .NET course, which also
includes a validation application block (in the Service Oriented
Architecture section of the course).
|