Nov 24 2008

Class vs Struct

Category: .Net | Software DesignRory Primrose @ 08:09

There is a lot of information around that discusses the differences between classes and structs. Unfortunately there isn't a lot of information available about when to use one over the other.

MSDN has a good resource which provides guidance on how to choose between classes and structs. It starts by describing the differences between the two and then provides the following advice.

Consider defining a structure instead of a class if instances of the type are small and commonly short-lived or are commonly embedded in other objects.

Do not define a structure unless the type has all of the following characteristics:

  • It logically represents a single value, similar to primitive types (integer, double, and so on).
  • It has an instance size smaller than 16 bytes.
  • It is immutable.
  • It will not have to be boxed frequently.

If one or more of these conditions are not met, create a reference type instead of a structure. Failure to adhere to this guideline can negatively impact performance.

The one that got me was having an instance size of 16 bytes or smaller. Several of the classes that I wanted to convert into structs defined string properties. Initially, I thought that a string would almost always be over 16 bytes making it inappropriate for a struct.

It later occurred to me that strings are reference types not value types. Any string variable is simply a pointer to the memory location that holds the data for that reference type. This means that the size of a string property in a struct is the size of IntPtr.

Structs are back on the menu.

Some useful links are:

Tags:

Nov 20 2008

Low impact WCF integration testing with SSL

Category: .Net | .Net | Personal | PersonalRory Primrose @ 19:18

I have been writing lots of tests for services and framework/toolkit type components over the last year. In both of these areas of development, I have written unit and integration tests that use WCF calls over SSL. Using SSL means that certificates are required. While an x509 certificate can be loaded from a file path, most implementations that use certificates rely on the certificate already being imported into a certificate store on the local machine. This means that the environment in which the tests are executed needs to be configured prior to running the tests.

This scenario limits flexibility for two reasons. Firstly, another developer can't get the solution from source control and simply run the tests as they won't have the required certificate installed. Secondly, a continuous build process will not be able to build the solution on a build machine that hasn't already been configured.

In trying to address this environment configuration issue, we did previously come up with a solution after discussions with some colleagues. Creating localhost certificates meant that developer and team build machines would be able to run unit tests without configuration changes to service addresses between those machines. It did however mean that each machine still needed manual configuration in order to install localhost certificates.

Recently, I have been migrating my custom username password service credential solution into the Neovolve.Toolkit project on CodePlex. I want to create unit and integration tests that are portable to multiple machines without any prior configuration of those machines before running the tests or any assumptions about software installation beyond Visual Studio and the .Net framework. This means that I need a good certificate solution for SSL support. I also want to avoid IIS and Cassini hosting of the services and to support running the tests from both XP and Vista. XP is easy to please, but Vista comes with some security considerations. The intention is to have a solution with unit and integration tests that will work 'out of the box'.

The answer

The answer is to use a NetTcpBinding, a pfx certificate with a private key, runtime installation/uninstallation of the certificate using the current user certificate store and specific configuration of the WCF service and client.

Major kudos goes to Mark Seemann for this post which describes the idea of using pfx certificates and provides the bulk of the code to install and uninstall the certificate before and after the tests.

This post touches on Mark's guide for pfx certificates and working with the certificate store. It will then describe in more detail how this scenario can be used to cleanly provide an SSL solution for testing WCF services under source control.

Generating the certificate

Firstly, the certificate needs to be created. Using makecert.exe, a certificate can be generated and obtained from the certificate store. The only thing that needs to be modified for your own scenario is the subject name of the certificate.

To create this certificate, open up the Visual Studio command prompt (elevated privileges required on Vista) and enter:

makecert -sr LocalMachine -ss My -a sha1 -n CN=Neovolve.Toolkit -sky exchange -pe

MakeCert

The 'Neovolve.Toolkit' is the subject name I have used in this case. You will probably want to use your own identifier.

Open up MMC and add the Certificates -> Computer account -> Local computer snap in.

ComputerCertificates

Go to Personal Certificates and locate the certificate that you created with makecert.exe. Right click this certificate and select All tasks -> Export. Continue through the wizard to export the pfx file with its private key.

I used the subject name as the password and the export file name so that it is easy to remember.

Solution configuration

The first thing to do is to add this pfx certificate file to your solution. If there are multiple projects that will use the certificate, the best way to manage the certificate is to add it as a Solution Item and link it to each project that will use it. Alternatively, if there is just one project that uses the certificate, add it directly to that test project. This is the easiest way to ensure that the pfx certificate is under source control and available to everyone who references the solution.

image

Next, we need to ensure that the certificate is copied to the target directory for the project. Go into the properties of the certificate and set the Copy to Output Directory setting to Copy if newer.

image

Given that this certificate is used for unit testing, MSTest doesn't know that this file in the output directory is required for the test run. We need to explicitly tell it to take this file when it copies the binaries to its test directory.

The best way to do this is to add a DeploymentItem attribute to the test method that uses it. I prefer this method instead of using deployment configuration in testrunconfig files. If the same test project is used in multiple solutions, each solution may use a different testrunconfig file which may not have the same deployment settings. Solutions may also use multiple testrunconfig files which presents the same problem. Using the DeploymentItem attribute puts the deployment responsibility as close to the test as possible and eliminates deployment/configuration problems for running the test.

For example:

[TestMethod]
[DeploymentItem(@"Communication\Security\Neovolve.Toolkit.pfx")]
public void PasswordSecurityOnThreadTest()
{
}

Note that in this solution, the pfx certificate is in a child directory structure which needs to be indicated in the attribute. This is required because the copy instruction on the properties of the file causes the same directory structure relative to the project file to be used when the file is copied to the output directory by the compiler. When the DeploymentItem attribute is interpreted, the file is copied to the same location as the binaries as no output path has been specified in the attribute.

Runtime certificate management

In order for this certificate to be used in a test that requires SSL, it must be installed into a certificate store on the local machine. Because we don't want to leave the machine in an inconsistent state, the certificate needs to be uninstalled when the tests have completed.

The CertificateDetails struct below contains the information required to install and uninstall the certificate.

using System;
using System.Security.Cryptography.X509Certificates;
 
namespace Neovolve.Toolkit.UnitTests.Communication.Security
{
    /// <summary>
    /// The <see cref="CertificateDetails"/>
    /// struct is used to store information about a certificate for installing and uninstalling a certificate in a certificate store.
    /// </summary>
    internal struct CertificateDetails
    {
        /// <summary>
        /// Stores the Filepath of the certificate.
        /// </summary>
        public String Filepath;
 
        /// <summary>
        /// Stores the password of the certificate.
        /// </summary>
        public String Password;
 
        /// <summary>
        /// Stores the store location of the certificate when the certificate is installed.
        /// </summary>
        public StoreLocation StoreLocation;
 
        /// <summary>
        /// Stores the store name of the certificate when the certificate is installed.
        /// </summary>
        public StoreName StoreName;
 
        /// <summary>
        /// Stores the subject of the certificate.
        /// </summary>
        public String Subject;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="CertificateDetails"/> struct.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <param name="subject">The subject.</param>
        /// <param name="password">The password.</param>
        /// <param name="storeName">Name of the store.</param>
        /// <param name="storeLocation">The store location.</param>
        public CertificateDetails(
            String filePath, String subject, String password, StoreName storeName, StoreLocation storeLocation)
        {
            Filepath = filePath;
            Subject = subject;
            Password = password;
            StoreName = storeName;
            StoreLocation = storeLocation;
        }
    }
}

Certificates can be installed and uninstalled using the following CertificateManager class. As Mark mentions in his post, keep in mind that if there is collision of subject names with existing certificates, those certificates will also be uninstalled. To avoid this, use a subject name that is unique enough to identify the purpose of the certificate without colliding with existing certificates in the store.

using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
 
namespace Neovolve.Toolkit.UnitTests.Communication.Security
{
    /// <summary>
    /// The <see cref="CertificateManager"/>
    /// class is used to install and uninstall certificates in the certificate store.
    /// </summary>
    internal class CertificateManager
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CertificateManager"/> class.
        /// </summary>
        public CertificateManager()
        {
            Certificates = new List<CertificateDetails>();
        }
 
        /// <summary>
        /// Installs the certificates.
        /// </summary>
        public void InstallCertificates()
        {
            // Loop through each configured certificate
            for (Int32 index = 0; index < Certificates.Count; index++)
            {
                CertificateDetails certificate = Certificates[index];
 
                // Install this certificate
                InstallCertificate(certificate);
            }
        }
 
        /// <summary>
        /// Uninstalls the certificates.
        /// </summary>
        public void UninstallCertificates()
        {
            // Loop through each configured certificate
            for (Int32 index = 0; index < Certificates.Count; index++)
            {
                CertificateDetails certificate = Certificates[index];
 
                // Uninstall this certificate
                UninstallCertificate(certificate);
            }
        }
 
        /// <summary>
        /// Installs the certificate.
        /// </summary>
        /// <param name="certificateDetails">The certificate information.</param>
        private static void InstallCertificate(CertificateDetails certificateDetails)
        {
            X509Certificate2 certificate = new X509Certificate2(
                certificateDetails.Filepath, certificateDetails.Password, X509KeyStorageFlags.PersistKeySet);
            X509Store store = new X509Store(certificateDetails.StoreName, certificateDetails.StoreLocation);
 
            try
            {
                store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);
 
                store.Add(certificate);
            }
            finally
            {
                store.Close();
            }
        }
 
        /// <summary>
        /// Uninstalls the certificate.
        /// </summary>
        /// <param name="certificateDetails">The certificate information.</param>
        private static void UninstallCertificate(CertificateDetails certificateDetails)
        {
            X509Store store = new X509Store(certificateDetails.StoreName, certificateDetails.StoreLocation);
 
            try
            {
                store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);
 
                // Loop through each certificate in the store in reverse order
                for (Int32 index = store.Certificates.Count - 1; index >= 0; index--)
                {
                    X509Certificate2 certificate = store.Certificates[index];
 
                    // Check if the subject names match
                    if (certificate.Subject
                        == certificateDetails.Subject)
                    {
                        store.Remove(certificate);
                    }
                }
            }
            finally
            {
                store.Close();
            }
        }
 
        /// <summary>
        /// Gets the certificates.
        /// </summary>
        /// <value>The certificates.</value>
        public List<CertificateDetails> Certificates
        {
            get;
            private set;
        }
    }
}

The next part of the process is to configure the certificates so they can be installed at the beginning of the tests and uninstalled at the end. For example:

using System;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neovolve.Toolkit.Communication.Security;
 
namespace Neovolve.Toolkit.UnitTests.Communication.Security
{
    /// <summary>
    /// The <see cref="PasswordServiceCredentialsTests"/>
    /// class contains unit tests for the 
    /// <see cref="PasswordServiceCredentials"/> implementation.
    /// </summary>
    [TestClass]
    public class PasswordServiceCredentialsTests
    {
        /// <summary>
        /// Stores the certificate manager.
        /// </summary>
        private static readonly CertificateManager certificateManager = new CertificateManager();
 
        #region Setup/Teardown
 
        /// <summary>
        /// Initializes the class.
        /// </summary>
        /// <param name="context">The context.</param>
        [ClassInitialize]
        public static void InitializeClass(TestContext context)
        {
            certificateManager.Certificates.Add(
                new CertificateDetails(
                    "Neovolve.Toolkit.pfx",
                    "CN=Neovolve.Toolkit",
                    "Neovolve.Toolkit",
                    StoreName.My,
                    StoreLocation.CurrentUser));
 
            certificateManager.UninstallCertificates();
            certificateManager.InstallCertificates();
        }
 
        /// <summary>
        /// Cleans up the class.
        /// </summary>
        [ClassCleanup]
        public static void CleanupClass()
        {
            certificateManager.UninstallCertificates();
        }
 
        #endregion
    }
}

This code identifies the pfx certificate using a relative path, the subject name used for the certificate and the password. The store location to use is CurrentUser. Using LocalComputer for the store would require elevated privileges in Vista. Because the certificate is being installed, used for testing and then uninstalled by the account running the tests, the CurrentUser store is still appropriate and avoids access denied errors.

The service host and client

My unit test is written with a self-hosted service and client that are both configured at runtime to avoid a reliance on IIS or Cassini. The following code is the initial code fragment of the unit test that sets up the host and client channel.

const String ServiceAddress = "net.tcp://localhost/PasswordServiceCredentialsTests";

Uri address = new Uri(ServiceAddress);
NetTcpBinding binding = new NetTcpBinding();

binding.Security.Mode = SecurityMode.TransportWithMessageCredential;
binding.Security.Message.ClientCredentialType = MessageCredentialType.UserName;

ServiceHost host = new ServiceHost(typeof(PasswordService));

// Add debug support to the host
ServiceDebugBehavior debugBehaviour = new ServiceDebugBehavior();

debugBehaviour.IncludeExceptionDetailInFaults = true;
host.Description.Behaviors.Remove<ServiceDebugBehavior>();
host.Description.Behaviors.Add(debugBehaviour);

// Create the service credentials
PasswordServiceCredentials credentials = new PasswordServiceCredentials();

credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;

// Assign the certificate from the CurrentUser store using the subject name of the certificate
credentials.ServiceCertificate.SetCertificate(
    StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "Neovolve.Toolkit");

host.Description.Behaviors.Remove<ServiceCredentials>();
host.Description.Behaviors.Add(credentials);

ServiceAuthorizationBehavior authorization = new ServiceAuthorizationBehavior();

authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom;
host.Description.Behaviors.Remove<ServiceAuthorizationBehavior>();
host.Description.Behaviors.Add(authorization);

host.AddServiceEndpoint(typeof(IPasswordService), binding, address);

try
{
    host.Open();

    // We need to identify that the identity of the certificate is valid for the address of the service
    EndpointAddress endpointAddress = new EndpointAddress(
        address, EndpointIdentity.CreateDnsIdentity("Neovolve.Toolkit"));

    ChannelFactory<IPasswordService> factory = new ChannelFactory<IPasswordService>(
        binding, endpointAddress);

    try
    {
        // We need to ignore certificate validation errors
        // Because the certificate was created with makecert, there is no chain of trust on this certificate
        factory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode =
            X509CertificateValidationMode.None;

        String userName = Guid.NewGuid().ToString();
        String password = Guid.NewGuid().ToString();

        factory.Credentials.UserName.UserName = userName;
        factory.Credentials.UserName.Password = password;

        factory.Open();

        IPasswordService channel = factory.CreateChannel();

The things to note about this code fragment are:

  • The NetTcpBinding is used to avoid the security restrictions of http addresses in Vista
  • The service certificate is identified by its subject
  • The service certificate is found in the CurrentUser store to avoid security restrictions on the LocalComputer store in Vista
  • The EndpointAddress used for the client channel is assigned an EndpointIdentity that identifies the subject of the certificate as a valid address for the service. This is important so that a certificate for Neovolve.Toolkit can be used with an address of localhost
  • Certificate validation is disabled for the client because the pfx certificate doesn't have a chain of trust that can be validated

Using this solution, a certificate can be under source control, be used to provide SSL support to test WCF services on both XP and Vista and not require initial configuration of IIS, Cassini or certificates.

Tags: , , , , ,

Nov 18 2008

Using StyleCop

Category: .Net | ApplicationsRory Primrose @ 06:45

StyleCop is a great tool for keeping code style consistent. I don't agree with all of the default rules though. Rules that you don't want can be disabled using a Settings.StyleCop file. By default, each project will use its own settings file. Having to maintain a settings file for each project is very cumbersome. Thankfully, there is a better way.

The Source Analysis blog has a post about how settings are inherited from parent directories. This makes sharing settings between projects very easy. Here are some ideas for how to set this up and make the settings file easy to work with.

These instructions assume that all the projects in the solution are in child directories under the location that the solution file resides in.

How to manage your StyleCop settings

If a Settings.StyleCop file doesn't already exist, right-click on a project and select StyleCop Settings. Remove any of the rules that you don't want applied and click OK.

Open Windows Explorer for that project and move the Settings.StyleCop file into the parent (solution) directory.

Right-click on the solution in Solution Explorer and select Add -> Existing Item.

Select Settings.StyleCop from the solution directory.

Add Existing Item

This will add a link to this file into the solution, but not specific to a project. For example:

Add as Solution Item

The next step is to ensure that the default application for this file in Visual Studio is the StyleCop settings editor.

Right-click the Settings.StyleCop file and select Open With.

Settings Editor

Ensure that StyleCopSettingsEditor is set as the default. If it isn't, select it and click Set as Default and click OK.

From now on, you can simply open the Settings.StyleCop file like any other file and it will open up in the settings.

Note: Using this configuration, don't edit the settings for StyleCop against the project directly as these settings will not be available to the other projects in the solution.

This is my current settings file: Settings.StyleCop (2.94 kb)

Tags: ,

Nov 12 2008

Creating event log sources without administrative rights

Category: .Net | ApplicationsRory Primrose @ 15:13

I have been coming up against a scenario where I need to create new event log sources in an existing event log at runtime.

Using System.Diagnostics.EventLog

Administrative rights are required to create event log sources using the System.Diagnostics.EventLog class. Without administrative rights, CAS kicks in and a SecurityException is thrown.

System.Security.SecurityException: The source was not found, but some or all event logs could not be searched.  Inaccessible logs: Security.

Attempting to create a new source or checking to see if the source already exists will enumerate all the event logs on the machine. This requires administrative rights because some event logs (such as the Security event log) are protected with specific security permissions. Without administrative rights, those event logs fail to be read.

Avoiding System.Diagnostics.EventLog

As administrative rights shouldn't be used to run applications and services, this is a bit of a problem. Almost every suggested solution on the net says to run the application with administrative rights. This is a really bad idea. If the application is a client application, the user's profile doesn't necessarily have administrative rights to begin with. If it is a service application, you don't want it to run with administrative rights as this would be a significant security risk (especially if it is an IIS based application).

A better workaround is to modify the registry directly rather than using the System.Diagnostics.EventLog class. Ideally, the installation of the application/service should create a custom event log for use by the application. Whichever event log is used, appropriate ACL's should be applied to the registry key for that event log so that the application has read/write permissions to that event log (see screen shot below). This allows new event log sources to be created directly without having to use the System.Diagnostics.EventLog class.

The general logic to achieve this is:

  • Connect to LocalMachine registry on the local/remote machine
  • Open key SYSTEM\CurrentControlSet\Services\EventLog\[YourEventLogNameHere]
  • Throw exception if the event log does not exist
  • Open key [YourEventLogSourceNameHere]
  • If the key is null (the source does not yet exist)
    • Create new sub key using [YourEventLogSourceNameHere]
    • Create new string value EventMessageFile in the new subkey with the value C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\EventLogMessages.dll

To set up the permissions to achieve this, the installation package needs to assign permissions to the registry key for the event log for the account used to run the application. The location of the event log in the registry is indicated in the first two points above.

The permissions should look something like this:

image

Update:

After further testing, the only permissions required for the event log are Set Value and Create Subkey. The above settings for Query Value and Enumerate Subkeys are not required. This is because when you run the above logic, querying for the existence of a key (by simply calling OpenSubKey) is not querying a value and isn't enumerating the keys. Once the source is created, no further registry permissions are required to write to the event log.

Tags:

Nov 7 2008

Strict IErrorHandler usage

Category: .NetRory Primrose @ 09:50

I previously posted about using IErrorHandler implementations for running error handling and exception shielding in WCF services. A few months after that post, Dave Jansen suggested that there are some risks in leaving it up to configuration to get the error handlers invoked.

The advantage of using configuration is that the behaviour of the service can be changed in a production environment without necessarily requiring the service itself to be recompiled. The disadvantage is that configuration could be either missing or incorrect.

Configuration is something that probably shouldn't change independent of the dev/test/release cycle of a service, especially when it comes to error handling and exception shielding. This in itself means that minimal flexibility is lost if the error handler is compiled into the service rather than wired up through configuration.

Failure to configure the service correctly for IErrorHandler implementations may pose a risk for two reasons. Firstly, without the intended error handlers being invoked, incorrect exceptions may flow across the service boundary. This occurs because there is no error handling in place. Secondly and more importantly, sensitive information in exceptions may also flow across the service boundary which could be a security risk. This occurs because there is no exception shielding in place.

An alternative to configuration is to use an attribute implementation to compile IErrorHandler implementations to the service implementation.

[ErrorHandler(typeof(KnownErrorHandler), typeof(UnknownErrorHandler))]
public class TestService : ITestService
{
}

In this example, there is a TestService implementation that uses the ITestService contract. The ErrorHandler attribute is used to define two error handlers for the service implementation, being KnownErrorHandler and UnknownErrorHandler.

Here is the attribute code.

using System; 
using System.Collections.ObjectModel; 
using System.Diagnostics; 
using System.ServiceModel; 
using System.ServiceModel.Channels; 
using System.ServiceModel.Description; 
using System.ServiceModel.Dispatcher; 
namespace Neovolve.Toolkit.Communication 
{ 
    /// <summary> 
    /// The <see cref="ErrorHandlerAttribute"/> 
    /// class is used to decorate WCF service implementations with a 
    /// <see cref="IServiceBehavior"/> that identifies <see cref="IErrorHandler"/> references to invoke 
    /// when the service encounters errors. 
    /// </summary> 
    /// <remarks> 
    /// <para> 
    /// The constructor of the attribute instance takes a <c>params</c> array of strings or types. 
    /// This allows the attribute to attach one or more error handlers to the service implementation. 
    /// </para> 
    /// <para> 
    /// <see cref="IErrorHandler"/> implementations are often set up for services using configuration. 
    /// This may create risks in cases where configuration is not correctly defined or configuration values are missing. 
    /// The result of this may be that exception shielding and error handling are not functioning as intended. 
    /// </para> 
    /// <para> 
    /// Lack of exception shielding may be a security risk because exception information crosses the service boundary 
    /// that was not intended. The details of the exception may contain sensitive information that the consumers of 
    /// the services should not be able to read. 
    /// </para> 
    /// <para> 
    /// Lack of error handling may not be a security risk, but will produce an unexpected behaviour for clients as 
    /// they consume the service. According to the contract, the client may expect to see a particular fault exception 
    /// being raised, but may instead receive a different fault. 
    /// </para> 
    /// <example> 
    /// The following example demonstrates how to use this attribute on a service implementation. 
    /// <code lang="C#"> 
    ///    // In this case, the ErrorHandler attribute is used to assign multiple error handlers 
    ///    [ErrorHandler(typeof(KnownErrorHandler), typeof(UnknownErrorHandler))] 
    ///    public class TestService : ITestService 
    ///    { 
    ///    } 
    /// </code> 
    /// </example> 
    /// </remarks> 
    [AttributeUsage(AttributeTargets.Class)] 
    [CLSCompliant(false)] 
    public sealed class ErrorHandlerAttribute : Attribute, IServiceBehavior 
    { 
        /// <summary> 
        /// Initializes a new instance of the <see cref="ErrorHandlerAttribute"/> class. 
        /// </summary> 
        /// <param name="errorHandlerTypes">The error handler types.</param> 
        public ErrorHandlerAttribute(params Type[] errorHandlerTypes) 
        { 
            const String ErrorHandlerTypeParameterName = "errorHandlerTypes"; 

            // Checks whether the errorHandlerType parameter has been supplied 
            if (errorHandlerTypes == null) 
            { 
                throw new ArgumentNullException(ErrorHandlerTypeParameterName); 
            } 

            if (errorHandlerTypes.Length == 0) 
            { 
                throw new ArgumentOutOfRangeException(ErrorHandlerTypeParameterName); 
            } 

            // Loop through each item supplied 
            for (Int32 index = 0; index < errorHandlerTypes.Length; index++) 
            { 
                Type errorHandlerType = errorHandlerTypes[index]; 

                // Check if the item supplied is null 
                if (errorHandlerType == null) 
                { 
                    throw new ArgumentNullException(ErrorHandlerTypeParameterName); 
                } 
            } 

            // Store the types 
            ErrorHandlerTypes = errorHandlerTypes; 
            ValidateHandlerTypes(); 
        } 

        /// <summary> 
        /// Initializes a new instance of the <see cref="ErrorHandlerAttribute"/> class. 
        /// </summary> 
        /// <param name="errorHandlerTypeNames">The error handler type names.</param> 
        public ErrorHandlerAttribute(params String[] errorHandlerTypeNames) 
        { 
            const String ErrorHandlerTypeNamesParameterName = "errorHandlerTypeNames"; 

            // Checks whether the errorHandlerTypeName parameter has been supplied 
            if (errorHandlerTypeNames == null) 
            { 
                throw new ArgumentNullException(ErrorHandlerTypeNamesParameterName); 
            } 

            if (errorHandlerTypeNames.Length == 0) 
            { 
                throw new ArgumentOutOfRangeException(ErrorHandlerTypeNamesParameterName); 
            } 

            // Loop through each item supplied 
            for (Int32 index = 0; index < errorHandlerTypeNames.Length; index++) 
            { 
                String errorHandlerTypeName = errorHandlerTypeNames[index]; 

                // Ensure that a value has been supplied 
                if (String.IsNullOrEmpty(errorHandlerTypeName)) 
                { 
                    throw new ArgumentNullException(ErrorHandlerTypeNamesParameterName); 
                } 
            } 

            ErrorHandlerTypes = new Type[errorHandlerTypeNames.Length]; 

            // Loop through each type 
            for (Int32 index = 0; index < errorHandlerTypeNames.Length; index++) 
            { 
                // Get a reference to the type 
                String errorHandlerTypeName = errorHandlerTypeNames[index]; 
                Type handlerType = Type.GetType(errorHandlerTypeName, true, true); 

                ErrorHandlerTypes[index] = handlerType; 
            } 

            ValidateHandlerTypes(); 
        } 

        /// <summary> 
        /// Provides the ability to pass custom data to binding elements to support the contract implementation. 
        /// </summary> 
        /// <param name="serviceDescription">The service description of the service.</param> 
        /// <param name="serviceHostBase">The host of the service.</param> 
        /// <param name="endpoints">The service endpoints.</param> 
        /// <param name="bindingParameters">Custom objects to which binding elements have access.</param> 
        public void AddBindingParameters( 
            ServiceDescription serviceDescription, 
            ServiceHostBase serviceHostBase, 
            Collection<ServiceEndpoint> endpoints, 
            BindingParameterCollection bindingParameters) 
        { 
            // Nothing to do here 
        } 

        /// <summary> 
        /// Provides the ability to change run-time property values or insert custom extension objects such as error handlers, message or parameter interceptors, security extensions, and other custom extension objects. 
        /// </summary> 
        /// <param name="serviceDescription">The service description.</param> 
        /// <param name="serviceHostBase">The host that is currently being built.</param> 
        public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) 
        { 
            // Loop through each channel dispatcher 
            for (Int32 dispatcherIndex = 0; 
                dispatcherIndex < serviceHostBase.ChannelDispatchers.Count; 
                dispatcherIndex++) 
            { 
                // Get the dispatcher for this index and cast to the type we are after 
                ChannelDispatcher dispatcher = serviceHostBase.ChannelDispatchers[dispatcherIndex] as ChannelDispatcher; 
                Debug.Assert(dispatcher != null, "The dispatcher collection returned an item of an incorrect type"); 

                // Loop through each error handler 
                for (Int32 typeIndex = 0; typeIndex < ErrorHandlerTypes.Length; typeIndex++) 
                { 
                    Type errorHandlerType = ErrorHandlerTypes[typeIndex]; 

                    // Create a new error handler instance 
                    IErrorHandler handler = Activator.CreateInstance(errorHandlerType) as IErrorHandler; 
                    Debug.Assert(handler != null, "Failed to create the IErrorHandler instance"); 

                    // Add the handler to the dispatcher 
                    dispatcher.ErrorHandlers.Add(handler); 
                } 
            } 
        } 

        /// <summary> 
        /// Provides the ability to inspect the service host and the service description to confirm that the service can run successfully. 
        /// </summary> 
        /// <param name="serviceDescription">The service description.</param> 
        /// <param name="serviceHostBase">The service host that is currently being constructed.</param> 
        public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) 
        { 
            // Nothing to do here 
        } 

        /// <summary> 
        /// Validates the handler types. 
        /// </summary> 
        private void ValidateHandlerTypes() 
        { 
            Debug.Assert(ErrorHandlerTypes != null, "No ErrorHandlerTypes is null"); 
            Debug.Assert(ErrorHandlerTypes.Length > 0, "No error handler types are available"); 

            // Loop through each handler 
            for (Int32 index = 0; index < ErrorHandlerTypes.Length; index++) 
            { 
                Type errorHandlerType = ErrorHandlerTypes[index]; 

                // Check if the type doesn't define the IErrorHandler interface 
                if (typeof(IErrorHandler).IsAssignableFrom(errorHandlerType) == false) 
                { 
                    // We can't use this type 
                    throw new InvalidCastException(); 
                } 
            } 
        } 

        /// <summary> 
        /// Gets or sets the error handler types. 
        /// </summary> 
        /// <value>The error handler types.</value> 
        private Type[] ErrorHandlerTypes 
        { 
            get; 
            set; 
        } 
    } 
} 

This attribute can be found in the Neovolve.Toolkit project in the Neovolve project on CodePlex. The Toolkit binary in the project downloads will include this attribute in the near future. Until then, you can access the code either here or from the CodePlex site.

Tags: ,