P3.NET

Using the FileSystemWatcher

(Originally published: 10 June 2007)

The FileSystemWatcher (FSW) component available in .NET is being used more and more every day.   Unfortunately FSW does not work quite the way you would expect.  This article will discuss how to use the FSW in a .NET application.  An additional problem with using FSW is performing complex tasks without impacting the performance of the system overall.   This article will discuss a basic approach to working with FSW while maintaining good performance.

What Is the FileSystemWatcher?

Before discussing how to use the FSW it is important to understand what it is and what it is used for. The FSW is a component used to monitor the file system for file or directory changes. The FSW can be used to receive notifications when files or directories are created, moved, deleted or updated. Although it is generally only useful in Windows applications it can be used in any type of application including web applications.

Under the hood the FSW relies on the underlying file system in Windows. As a result the events raised by FSW are low level. However they are accurate and fast, being raised by the file system as soon as the operation occurs. No buffering of events occurs either. This will prove to be problematic as we will see later. FSW identifies the following events for both files and directories: 

  • Create
  • Delete
  • Change
  • Rename

To avoid raising too many events the FSW also supports filtering of files and directories based on a file path or a file mask. This is very useful as the file system is used a lot. It would be wasteful to receive every event raised by the file system if all we cared about was the modification of a specific file.  To demonstrate how to use FSW refer to the simple file monitoring application. FileMonitor will monitor any folder it is pointed at. As events are raised it will log the event data to a simple UI control. To keep things simple the application simply exposes a PropertyGrid for interacting with the FSW properties. Run the sample application as you read through the article so that you can better understand what is going on.

Configuring the FileSystemWatcher

The FSW is a standard .NET component. It can be dragged from the Toolbox in Visual Studio and dropped onto a form or created programmatically in code. It is generally easier to create and use the FSW programmatically rather than dragging and dropping it onto a parent form. For the sample application however dragging and dropping the component from the Toolbox works just fine. The FSW resides in the System.IO namespace since it is a general component rather than a WinForms component.

Before the FSW can be used you must configure the events you are interested in and apply any filtering desired. Once the FSW is configured you must enable it using the EnableRaisingEvents property. This property starts (or stops) the FSW. We do not want to set the property until we have fully configured the component otherwise we might miss some events.

The Path property indicates the folder to be monitored. If the IncludeSubdirectories property is set then any subfolder is also monitored. Use this property with care. If you set Path to a folder with many subdirectories you might get more events then you want.

Set Path to some folder on your machine (for discussion purposes assume c:temp). Leave IncludeSubdirectories as false and set EnableRaisingEvents to true. The FSW is now watching the given folder for any changes made to the file name, directory name or last write time. Go to Explorer and copy a file into the folder. Notice that several events were generated. We will discuss why shortly. Rename the file and then delete it. In each case more events are raised. As you can see from the list the full path to the file is given each time along with the event that occurred.

Now set IncludeSubdirectoriesto true and create a new folder in the folder from earlier. Copy a file from the original folder to the new folder. In each case you should see some events being raised. It appears that you can change the properties of the FSW without

FileSystemWatcher fsw = new FileSystemWatcher(); 
fsw.Path = @”c:temp”
fsw.IncludeSubdirectories = true
fsw.SynchronizingObject = mainForm; 
fsw.EnableRaisingEvents = true

 

Listening For Events

Since the FSW is a standard .NET component it raises events in the same way as everybody else. The FSW only defines five events of interest.

We will ignore the Error event. Each event does exactly what you think it does. All the events except for Renamed have an FileSystemEventArgs argument. This argument specifies the following useful properties.

  • ChangeType – An enumeration identifying the change (in lieu of the event).
  • FullPath – The full path and name of the file or folder causing the event.
  • Name – The name (without path) of the file or folder causing the event.

The Renamed event has an argument of type RenamedEventArgs which derives from FileSystemEventArgs. It adds or modifies the following properties.

  • FullPath – The full path to the new file or folder.
  • Name – The name of the new file or folder.
  • OldFullPath – The original path of the file or folder.
  • OldName – The original name of the file or folder.

In the sample application an event handler is defined for each event. A formatted message is generated in the list box containing the event arguments.

I know what you are thinking. “What a minute! You have a threading issue in your code”. Since the FSW is not a WinForms component and is called by the operating system there is a good chance that the event will be raised on a non-UI thread. You are absolutely correct…sort of. The event will indeed be invoked from a non-UI thread. However since the component is generally designed for WinForms applications it is built to work without too much effort. Internally the component will transition to the UI thread before it raises the event. Therefore you can safely access the UI from within any event handler hooked up to the FSW.

FileSystemWatcher fsw = new FileSystemWatcher(); 

fsw.Changed += OnFileChanged; 
fsw.Created += OnFileCreated; 
fsw.Deleted += OnFileDeleted; 
fsw.Renamed += OnFileRenamed; 
fsw.EnableRaisingEvents = true

 

Filtering Events

The FSW supports two different types of filtering: file/folder name and change type. The Filter property is used to filter the events based upon a file/folder name. It supports standard wildcard characters so if you want to monitor a specific file specify the file name in the Filter property. If you want to monitor a type of file (such as text files) then enter the appropriate mask (such as *.txt). Note that only a single mask can be used. Do not attempt to use a semicolon to separate multiple file masks because it will not work.

The other filtering option, change type, is available strictly for Changed events. The Changed event is raised when anything about the file changes including its name, size or even its attributes. Generally all you care about is if a file is modified. Receiving a change notification when a file changes from readonly to read-write is wasteful. Enter the NotifyFilter property. This property is a list of one or more enumerated values indicating the types of information for which a change notification should be generated. The default is whenever the name or last write date changes. You can watch for other changes as well such as attribute changes or even when the file was last modified.

The only downside to the NotifyFilter is that when the Changed event is raised there is no way to tell why it was raised. Ideally the ChangeType property on the event argument would indicate the exact change that occurred but it does not. It will always say the same thing. Therefore if you care about the exact change that was made you will need to keep track of that information yourself.

For the Renamed event if either the old or new file matches Filter then the event is raised.

Demystifying FSW Events

One of the most confusing issues with using FSW is figuring out why certain events are not raised. It is a common misconception that if you modify a file that you will receive a change event. This is not necessarily true. We will now discuss a few common file monitoring scenarios and discuss how to handle each one.

Monitoring for Creation

There are basically two ways to create a file: create a new file or rename an existing file. To successfully detect the creation of a file you should monitor the following events.

  • Created
  • Renamed

Created is raised in all cases if you have no filter and only if FullPath matches the filter otherwise. For Renamed you have to do a little more work if you are using a filter. If FullPath matches the filter then a file has been renamed to a file type that you care about. If OldFullPath matches the filter then a file has potentially been renamed from a file type that you care about. Notice I said potentially. You can rename a file without changing its file type. Therefore use FullPath first and only use OldFullPath if needed to determine what the event is signifying.

private void OnFileCreated ( object sender, FileSystemEventArgs e ) 

    Display(“Created file {0}”, e.FullPath); 
}

private void OnFileRenamed  ( object sender, RenamedEventArgs e ) 

    Display(“Renamed file {0} to {1}”, e.OldFullPath, e.FullPath); 
}

 

 Monitoring for Deletion

As with creation there are two ways to delete a file: delete the file or rename an existing file. To successfully detect the deletion of a file you should monitor the following events.

  • Deleted
  • Renamed

Deleted is raised in all cases if you have no filter and only if FullPath matches the filter otherwise. Renamed works the same way as it does for file creation.

A special case exists for deletion of a directory with subdirectories and/or files. A deletion event will be raised for each file in the directory (and each subdirectory if IncludeSubdirectories is set). Additionally if subdirectories are being monitored then a change event will be raised for each directory in which a file or directory was deleted.

private void OnFileDeleted ( object sender, FileSystemEventArgs e ) 

    Display(“Deleted file {0}”, e.FullPath); 
}

 

 Monitoring for Changes

Think about how a program might save data to an existing file called mydata.dat.  The naïve approach would be to open the original file and overwrite whatever was there. Although this is fast and easy to do if something goes wrong during the save process not only are the changes lost but so is the original file. Almost everyone has experienced this at least once. Now think about the events that would be involved.

  • Change for mydata.dat
  • Change for mydata.dat

The “superior” approach would be to create a temporary file, save the file to the temporary file, delete the original file and then rename the temporary file to the original file. Although there is more work involved and an opportunity for failure at no point would the original and changed data be lost. You could expect to see the following events.

  • Create of someTemp.tmp
  • Change of someTemp.tmp
  • Change of someTemp.tmp
  • Delete of mydata.dat
  • Rename from someTemp.tmp to mydata.dat

If you only monitor events for mydata.dat you would completely miss the saving of the file. Therefore you will often need to use heuristics to decide what events to process and what events to ignore. The following events should be monitored.

  • Changed
  • Deleted
  • Renamed

Changed is raised whenever the file changes. Remember that the Win32 API is used behind the scenes and it is low level. Therefore you can receive several Changed events for a single logical change in an application. You must handle this case.

Deleted is raised when the file is deleted. This is normally considered to be a change of the file although how it is handled is application specific.

Renamed is the most difficult event to handle. Go back to the file change discussion of earlier as you read this description. If FullPath is the file of interest (mydata.dat) then you can assume that the superior approach was used. Treat this case as though the entire file has changed. If OldFullPath is the file (mydata.dat) then treat it as though a deletion has occurred.

private void OnFileChanged ( object sender, FileSystemEventArgs e ) 

    Display(“Changed file {0}”, e.FullPath); 
}

 

 Locked Files

You will not get to far into using the FSW before you will want to do something with the file that you are receiving the event about. You might want to copy the file to another directory or perhaps read the new file contents. Remember, again, that the events are ultimately raised in the low level details of the operating system. There is a real good chance that the application that is manipulating the file still has a lock on it.  Some applications allow shared read access when they update the file while other applications deny all access to the file. Therefore it is generally a good idea to assume that, while processing the event, the file in question is locked. This puts quite a limitation on what you can do with the file.

To work around a locked file there is little option but to wait until the lock is released. Rarely is the lifetime of a lock predictable so generally the file has to be polled. Depending on the application polling every second or every couple of seconds should be sufficient. Polling generally involves just trying to get access to the file. Since file operations generally throw exceptions when things go wrong exception handling will probably be needed. It will not perform well but we will handle that later.

A final issue with dealing with locked files is the problem of leaked locks. An application may refuse to release a lock for whatever reason. It is generally not a good idea to continually poll for a file. After some point it is best to just give up. Therefore it is a good idea to define a retry count for each event. After the retry count is exceeded report an error and ignore the event. Depending on the application this could require that all additional events for the same file be ignored as well.

A complication added by locked files is how to handle other events. Ideally a single locked file should not stall all file processing. If file A is locked events for file B should still be processed. This will quickly become complicated due to file reuse. File reuse occurs when a file name is reused for several different files. Examples of file reuse include renaming a file and deleting a file and then recreating it (i.e. changing a file). Here is a sample of events that can cause file reuse.

  • File B is created
  • File B is changed
  • File A is deleted
  • File B is renamed to file A
  • File B is created
  • File B is changed

If file A is locked then file B can not be renamed nor can any subsequent events against file B be handled until file A is complete. Tracking this can become quite complex. In most applications it is not worth the effort therefore processing events in order (with retry counts) is a simple, reasonable requirement.

Buffering of Events

The sample application simply logs messages to the UI. In general however you will be doing far more work. Work that will take a reasonable amount of time (such as retry counting). The FSW communicates to the underlying file system through a shared buffer. This buffer is limited in size. If more events come from the file system than the FSW can handle the buffer will overflow. In this case two things will happen: you will miss events and the Error event will be raised.

If you start missing events then there is little you can do about it. Therefore the best option is to ensure that any FSW event handler processes the event as quickly as possible. This pretty much mandates that you push the actual event processing to a secondary thread. Moving work to a secondary thread is pretty standard fare in most applications. Therefore we will forego the usual discussions of thread safety and synchronization and instead focus on solving the general problem of handling FSW events on a secondary thread.

The general algorithm would work as follows.

private void OnSomeFswEvent ( object sender, FileSystemEventArgs e )
{
    //Create instance of event data structure containing the event type and file(s) of interest 
    //Lock the shared queue for writing 
    //Push the event data onto the queue    
    //Unlock the shared queue 
}

private void SomeWorkerThread ( )
{
    //While not terminating sleep for a fixed period of time  
    //    While there are events in the queue 
    //        Lock the shared queue 
    //            Pop the event data 
    //         Unlock the shared queue 
    //          Process the event 
    //    If a request to terminate was received 
    //        Terminate the worker thread 
}

 This is a pretty straightforward algorithm that should be easy to implement. Returning to the issue of file locks from earlier this algorithm introduces some issues however. Using the simple approach to locked files if a file is locked then the worker thread needs to stop processing any more events until the original event is processed or it times out. A local variable can be used to store the event. The variable needs to be checked before processing any events from the queue. Here is an updated algorithm with the changes in bold.

private void SomeWorkerThread ( )
{
    //While not terminating sleep for a fixed period of time  
    //    If current is set 
    //        Process the event 
    //    If failed 
    //        Increment current retry count 
    //    If retry count exceeds maximum 
    //        Report error 
    //        Clear current 
    //    Else 
    //        Clear current  
    // If current is empty  
    //    While there are events in the queue 
    //        Lock the shared queue 
    //            Pop the event data into current 
    //        Unlock the shared queue 
    //        Process the event 
    //        If failed 
    //            Increment current retry count 
    //        Else 
    //            Clear current 
    //    If a request to terminate was received 
    //        If current is set 
    //            Report error 
    //        Terminate the worker thread 
}

 

Enhancements

Access to the queue is serialized. This is overly restrictive given that the only time there will be a conflict is when FSW is trying to insert the first item into the queue while the worker is trying to retrieve it. A better synchronization approach may be useful. A bounded queue (MSDN Magazine, CLR Inside Out, May 2007) would solve the problem nicely and improve performance.

The second scalability issue involves the worker thread itself. Depending upon how many events are received the worker thread can easily get behind. Multiple worker threads would allow better scalability such that many events can be handled simultaneously. Ignoring locked files this is pretty straightforward to handle since multiple worker threads would access the same shared queue. However locked files make using multiple worker threads very complex. Some sort of handshaking must occur such that locked files are shared across multiple worker threads to ensure events are handled in order.  A better approach might be to use a single worker thread but have the worker thread be state aware for each of the files being processed.  Some events can be handled in parallel (even in the case of errors) provided the events do not work on the same set of files.  This is actually a lot harder than it sounds because of the possibility of files being renamed but nevertheless would solve the problem nicely.

Caveats

A few final caveats are in order about FSW. You can not use FSW on read only media like CDs or DVDs. There really is not much benefit anyway. You also can not use FSW on removable media.

If Path does not exist then FSW will attempt to create it when EnableRaisingEvents is set. The FSW requires read access to Path otherwise an exception will occur.

Earlier it was stated that you could call UI methods in the event handlers without worrying about what thread you are on. That is not entirely true. If you drag and drop the component onto a form then it will work. However if you programmatically create the component then it is not. The SynchronizingObject property of FSW controls this. When you drag and drop the component onto a form the form is assigned to the SynchronizingObject property. This causes the event handlers to be raised on the UI thread. When you programmatically create the component you should manually set the SynchronizingObject property to the main form or another UI control so synchronization will occur automatically.

You might think that filtering will help alleviate the buffering problem but it will not. Filtering occurs in FSW. Therefore the event must still travel from the file system to FSW through the shared buffer. There is little you can do besides processing events as fast as you can to avoid buffering issues.

Demystifying Auto Properties

There seems to be a lot of confusion going around about auto properties in C# and, starting with Visual Studio 2010, VB. Quite a few people are erroneously comparing them to fields and then trying to compare the two approaches. This is just plain wrong. This article attempts to clarify exactly what auto properties are and when they are useful. Before diving into auto properties it is important to differentiate between properties and fields and when to use each.

Fields

A field is a type member that holds a value. Every field has a type.  A field can be read or written. There is not any way to limit code to read or write access. The best that can be done is limit the field using the standard accessibility (public, protected/family, internal). A field simply stores a value so any (legal) value can be stored (if it gets past the compiler).

Fields, because they do little, are very fast to access and use. Fields execute no code when they are accessed so there is no worries about custom code being executed. All in all fields are low level. Because fields are so low level they can do almost nothing other than store a value. If a value is type-compatible with a field then it can be stored there irrelevant of any business rules or assumptions that might be desired. The following code demonstrates a few simple fields.

class Employee
{
   public int Id;

   public bool IsFullTime;
   public EmployeeType Status;

   public string FirstName;
   public string LastName;
}

 

Properties

A property is a type member that looks like a field but acts like a method. Every property has a type just like a field.  A property stores no data on its own. In fact a property is actually just a set of two methods: a getter and a setter. These accessors are used to get or set the value of a property. The getter has no parameters and returns the same type as the property.  The setter accepts a single parameter (usually) of the property’s type and returns nothing.

Most properties have both accessors but some have only a getter and, rarely, a setter. A property with only a getter cannot be assigned a value whereas a property without a getter can not be retrieved.

Properties are generally one of two types: a field wrapper or a computed field. A field wrapper property has a field that backs the property value, known as the backing field. Field wrappers are commonly used to add validation to field assignments. A computed field property generally only has a getter and returns a calculated value. The calculated value is often based upon some underlying field values but not always.  The following demonstrates a class with some properties.

class Employee
{
   public int Id 
   {
      get { return m_id; }
      set { m_id = value; }
   }

   public bool IsFullTime
   {
      get { return m_type == EmployeeType.Full; }
   }

   public EmployeeType Status
   {
      get { return m_status; }
      set { m_status = value; }
   }

   public string FirstName rstName
   {
      get { return m_firstName ?? “”; }
      set { m_firstName = value; }
   }

   public string LastName astName
   {
      get { return m_lastName ?? “”; }
      set { m_lastName = value; }
   }

   private int m_id; p;m_id;
   private EmployeeType m_status;

   private string m_firstName;
   private string m_lastName;
}    

In the above code most of the properties are wrapper properties.  IsFullTime is a computed property.  Notice that most wrapper properties simply get or set a backing field value.  This is pretty typical code.

Properties vs Fields

One of the big areas of debate is when to use properties and when to use fields.  Properties provide two very powerful features: field-like access and accessors.  A property is accessed just like a field.  In the following code can you identify which value is the field and which is the property?

Thread.CurrentThread.Join(Timeout.Infinite);

CurrentThread is a property of Thread while Infinite is a field of Timeout.  Yet they both look and behave like fields.  The CurrentThread getter is used to get the current thread from the Thread class.  The Infinite field is used to determine how long to wait.  This makes it real easy to use properties.  Note however that the underlying generated code is not the same for properties and fields.  For fields the underlying value is read or written directly.  But for properties a call to the getter or setter is inserted into the code.  Hence properties can be, theoretically, slower.

Because the property accessors are used to access the property you can execute arbitrary code whenever a propery is read or written.  This allows for things like validation, redirection or logging.  In the code given earlier the FirstName and LastName properties ensured that the string properties always returned a non-null value (a guideline that should be followed).  If a field would have been used instead then the caller would have had to handle the case of a null value.

The general guideline is to use public properties to expose private fields.  Even if the property simply returns the field the benefits outway the disadvantages.  For one if, at a later date, validation or some other code needs to be executed when accessing the field (or if the field is removed altogether) then a property accessor can be modified without breaking compatibility with existing code.  If a field is used then it is a breaking change to convert code from using a field to using a property.  In other words all the existing code would have to be recompiled before the “new” property would be used.

Public fields are not all bad.  Actually they are except for the very special case of read only fields.  It is generally OK to expose a public field if the field is read only (or constant) and is unlikely to change.  In all other cases expose a property instead.  Protected fields should generally be avoided as well.  Create a private field and expose a protected property.  Remember that a protected member is still public to derived types so it should follow the same rules as public members.

Where the debate often comes in is the performance.  Many people will point out that fields are faster than properties and this is technically true.  But the JIT can optimize property calls to all but eliminate the actual accessor call so the actual runtime performance will be negliable in almost all cases.  In general the 80/20 rule of performance tuning will eliminate any need to use public fields to avoid property performance issues.  Given the advantages of properties and their seamless integration into code there really is no good reason not to use them.

Auto Properties

The only real disadvantage of properties is all the code you have to write.  For just a simple wrapper you have to write the property declaration, the two accessors and the backing field.  Even worse is that the getter generally just returns the field while the setter just sets it.  This is ideally suited for code generators since it is boiler plate code.  In fact there are code snippets to do just that.

But if it is so easy to generate then why can’t the compiler just do it for us?  Well, as of VS 2008, it can.  Enter auto properties.  An auto property is nothing more than a wrapper property and its backing field.  The difference is that it can be defined in a single line of code.  Here is the Employee class using auto properties.

class Employee
{
    public int Id { getset; }

    public bool IsFullTime
    {
        get { return Status == EmployeeType.Full; }
    }

    public EmployeeType Status { getset; }

    public string FirstName
    {
        get { return m_firstName ?? “”; }
        set { m_firstName = value; }
    }

    public string LastName
    {
        get { return m_lastName ?? “”; }
        set { m_lastName = value; }
    }
            
    private string m_firstName;
    private string m_lastName;
}    

The Id and Status properties have been converted to auto properties.  Notice the backing fields were removed and the bodies of the accessors have been as well.  In this case the properties allow get and set operations but either keyword can be removed to make the property read-only or set-only.  The big advantage here is less code to read and write.  The name properties cannot be converted to auto properties because they do more than get or set the backing field.  Furthermore the IsFullTime property remains unchanged because it is a computed property and had no backing field anyway.

What’s going on behind the scenes here?  It is actually pretty straightforward.  When the compiler sees an auto property it generates a private backing field for the property.  Then the compiler generates the accessor(s) to get or set the backing field.  Basically all that boilerplate code is generated for you.  So whenever you need a property without any custom accessor code, use an auto property to save yourself some typing.

One of the questions often asked in the forums is why someone should use a field over an auto property.  The answer is that you should use a property over a field in almost all cases.  Whether that property is a normal property or auto property is not relevant.  An auto property just means that the compiler generates the property definition for you.  There is absolutely no other differences.  None.  Nadda.

Compatibility

Great new feature, but what if you are stuck on .NET v2?  Well here is the good news – the compiler generates all the code.  The VS 2008 compiler for C# (and VS 2010 for VB) generate all the code during compilation.  There is no runtime support needed.  Therefore the version of .NET you are using (other than v1.x) does not matter.  In fact there is no real way of distinguishing between auto properties and normal properties at runtime.  The best you can do is dump the code and look at the field name to see if it matches something the compiler might generate.  The point is that you can use this feature on any version of .NET provided you are using at least the VS 2008/2010 version of the C#/VB compiler. 

So, in summary, use an auto property whenever you have a wrapper property with no custom accessor code.  Use public properties (normal or auto) in lieu of public fields in every case except for readonly fields which may be exposed publicly.  Do not worry about the performance differences unless your performance tuning determines that the properties are truly slowing down your application.

Creating Configuration Sections

The .NET configuration subsystem is used throughout the framework.  Entire subsystems rest on top of it including security policies, application settings and runtime configuration.  ASP.NET uses the configuration subsystem heavily.  Applications can take advantage of the subsystem as well by creating their own configuration sections.  Unfortunately it is not straightforward.  This article will discuss how to create new configuration sections in .NET.

A point of clarification is needed.  Application settings, while relying on the configuration subsystem, are not related to configuration sections.  This article will not discuss the creation or usage of application settings.  Application settings, for purposes of this article, would be though settings that you can define in a project’s property pages.

History

Since the configuration subsystem is used throughout the framework it has been available since the initial release of .NET.  In v1.x you could extend the configuration subsystem by implementing the IConfigurationSectionHandler.  This interface boiled down to parsing XML elements.  While usable it was a little much to implement.  It also didn’t allow much more than reading in XML attributes and translating them into usable values.

In v2.0 the v1.x interface was wrapped in a more complete, complex set of classes.  The v2.0 subsystem allows for declarative or programmatic declaration of strongly type configuration properties, nested configuration settings and validation.  The newer subsystem also allows reading and writing configurations.  Additionally the need to manually parse XML elements has been all but removed.  While this makes it much easier to define configuration sections it also makes it much harder to deviate from the default behavior. Adding in sparse documentation and confusing examples results in a lot of postings in the forums about how to get things to work.

This article will focus exclusively on the v2.0 classes.  While we will not discuss the v1.x interface any it is important to remember that it still exists and resides under the hood.

Are We there Yet?

Rather than building up an example as we go along we are going to first take a look at the configuration that we ultimately want to be able to read.  We will then spend the rest of the time getting it all set up.  This is a typical design approach for configurations.  You know what you want.  All you need to do is get the subsystem to accept it.

For this article we are going to build a configuration section for a hypothetical test engine.  The test engine runs one or more tests configured in the configuration file.  The engine simply enumerates the configured tests and runs them in order.  Here is a typical section that we will want to support.  This will be refered to as the target XML throughout this article.

<tests version=”1.0” logging=”True“>
    <test name=”VerifyWebServer” 
          type
=”TestFramework.Tests.ServerAvailable
          failureAction=”Abort“>
        <parameter name=”url” value=”http:\www.myserver.com“>
    </test>
    <test name=”CheckService1” 
          type
=”TestFramework.Tests.WebServiceInvoke” 
          async
=”true” timeOut=”120“>
        <parameter name=”url” 
                   value
=”http:\www.myserver.comservice1.asmx” />
        <parameter name=”parameter_1” value=”hello” />
        <parameter name=”returns” value=”HELLO” />
    </test>
</tests>

Each test is represented by an XML element and all tests are contained in a parent tests element.  Each test must have a unique name and a type attribute.  The type attribute is used by the engine to create the appropriate test class to run.  A test can have some optional attributes as well.  The failureAction attribute specifies what should happen if the test fails (continue, abort, alert).  The default is to continue.  The async attribute is a boolean value indicating whether the test should be run synchronously or asynchronously.  The default is false.  The timeOut attribute specifies how long to wait for the test to complete (in seconds) and is only meaningful for async tests.  The default is 10 seconds.  

Configuration Files

The configuration subsystem must be able to map each XML element to a .NET type in order to be able to read it.  The subsystem refers to a top level XML element as a configuration section.  Generally speaking each configuration section must map to a section handler.  This is where the v1.x configuration interface mentioned earlier comes in.  When creating a new configuration you must define the configuration section that the subsystem will load.  Let’s take a look at a standard application configuration for a moment.  This will be refered to as the example XML throughout this article.

<?xml version=”1.0” encoding=”utf-8” ?>
<configuration>    
    <configSections>
        <section name=”tests” 
            type
=”TestFramework.Configuration.TestsSection,ConfigurationTest” />
    </configSections>

    <tests>
        <!– Tests go here –>          
    </tests>        
</configuration>

All XML elements will reside inside the configuration root element.  In the above file the application has defined a new section for the tests and created an empty tests element where the tests will go.  Every section inside the configuration element must have a  section handler defined for it.

To define a custom section in the configuration file we have to use the configSections element.  This element might already exist in the file.  It is a best practice to always put it as the first section in the file.  In the above example the element tells the subsystem that whenever it finds a tests element it should create an instance of the TestFramework.Configuration.TestsSection class and then pass the XML on for processing.  The type is the full typename (including namespace) followed by the assembly containing the type.  It can be a full or partial assembly name.  If the subsystem cannot find a section handler for an XML element or the type is invalid then the configuration subsystem will throw an exception.

“So I have to define a section handler for every XML element?  That’s nuts, forget it.”  Well, not exactly.  Firstly the configuration subsystem only cares about the top level XML elements (anything direct under configuration).  Child elements do not need a configuration section (but they do need a backing class as we’ll discuss later).  Secondly the subsystem supports section groups.  Section groups can be used to group sections together without requiring a handler.  The framework itself generally separates sections by the namespace they are associated with.  For example ASP.NET configuration sections generally reside in the system.web section group.  You define a section group in the configSections element like so.

<?xml version=”1.0” encoding=”utf-8” ?>
<configuration>
    <configSections>
        <sectionGroup name=”testFramework“>
            <section name=”tests” 
                 type
=”TestFramework.Configuration.TestsSection,TestFramework.Configuration” />                 
        </sectionGroup>
          
    </configSections
    
    <testFramework>
        <tests>
            …                 
        </tests>          
    </testFramework>
</configuration>

Section groups are useful for grouping together related sections. Groups can be nested inside other groups.  We will not discuss section groups further as they have no impact on section handlers.  A section handler does not care about anything above it in the XML tree.

A question you may be wondering about is why you do not see any section definitions for the framework sections.  That is because the subsystem actually looks for section handlers in the application, machine and domain configuration files.  The machine and domain configurations reside in the framework directory and can be configured by an administrator to control .NET applications.  If you were to look into these files you will eventually find section handler definitions for each of the pre-defined sections.  Furthermore you can look into the system assemblies and find their corresponding handler classes.  There is nothing special about the pre-defined sections.

A final note about configuration files if you have never worked with them.  When you add an application configuration file to your project it will be called app.config.  The runtime expects the configuration file to be named after the program executable (so prog1.exe would be prog1.exe.config).  The IDE will automatically copy and rename the app.config project item to the appropriate name and store it with the binary during a build.  Any changes you make to the configuration file (in the output directory) will be lost when you rebuild. 

Mapping XML to .NET

Before diving into the .NET code we need to understand how the subsystem will map the XML data to .NET.  The subsystem uses sections, elements and properties to map from XML elements and attributes. 

A configuration element is a class that derives from ConfigurationElement.  This class is used to represent an XML element.  Every XML element will ultimately map to a configuration element.  XML elements are normally used to house data that is too complex for a simple attribute value.  Elements are also used for holding collections of child elements.  Configuration elements will be used, therefore, to represent complex objects and/or parent objects.

A configuration property is ultimately represented by a ConfigurationProperty.  Normally, however, we apply an attribute to a class property to define them so the actual declaration will be hidden.  Configuration properties represent XML attributes.  Every XML attribute associated with an XML element will map to a configuration property on the corresponding configuration element.  As we will discuss later properties can be optional, have default values and even do validation.

A configuration section is a special type of configuration element.  A section derives from ConfigurationSection, which itself derives from ConfigurationElement.  For our purposes the only distinction is whether the element is a top level element or not.  If the element is a top level element that is defined in the configSections of the configuration file then it will be a configuration section (and derive from the appropriate class).  For all other purposes it works like an element. 

The configuration subsystem requires that every XML element and attribute be defined by either a configuration element/section or configuration property.  If the subsystem cannot find a mapping then an exception will occur.  We will discuss later how we can have some control over this.

To start things off we will define the configuration section for our example.  Following the precedence set up by the framework we will isolate our configuration classes to a Configuration subnamespace.  We will name configuration sections as -Section and elements as -Element.  The beginning name will match the XML element name but using Pascal casing.

Here is how we would define our configuration section. 

using System;
using System.Configuration;

namespace TestFramework.Configuration
{
   public class TestsSection : ConfigurationSection
   {
      …
   }
}

We will fill this class in as we progress.  At this point we have discussed enough to get the subsystem to load our example XML and return an instance of the TestsSection class.

Configuration Manager

In v2+ you will use the ConfigurationManager static class to interact with the configuration subsystem.  This class provides some useful functionality but the only one we care about right now is GetSection().  This method requires the name of a section and, upon return, will give us back an instance of the associated configuration section class with the configuration data.  Here is how our test engine would get the list of tests to run. 

using System;
using System.Configuration;> 
using TestFramework.Configuration;

namespace TestFramework
{
   class TestEngine
   {
      public void LoadTests ( )
      {
         TestsSection section = 
             ConfigurationManager.GetSection(“tests”as TestsSection;
         …
      }
   }
}

The subsystem will only parse a section once.  Subsequent calls will return the same data.  If the file is changed externally then the changes will not be seen without restarting the application.  You can force the subsystem to reload the data from disk by using ConfigurationManager.RefreshSection.

ConfigurationManager is in the System.Configuration namespace of the same named assembly.  This assembly is not automatically added as a reference so you will need to add it manually.  For those of you familiar with the v1.x subsystem, especially AppSettings, note that most of the classes are obsolete.  You should use ConfigurationManager for all new development.

As an aside almost all errors in the subsystem will cause an exception of type ConfigurationException or a derived class.  It can be difficult to tell from the exception what went wrong.  Badly formed XML or a missing section handler, for example, will generate a generic error saying the subsystem failed to initialize.  Use exception handling around all access to the subsystem but do not expect to be able to generate useful error messages from the resulting exception.

Configuration Properties

XML elements normally have one or more attributes associated with them.  In the target XML the tests element has a version and logging attribute.  The version is used for managing multiple versions of the engine and must be specified.  The logging attribute specifies that the engine should log all test runs.  It defaults to false.  Modify the example XML to include these attributes.  Trying to load the tests at this point will cause an exception because the attributes are not supported by the configuration section.

For each attribute on a section/element a configuration property must be defined.  Configuration properties can be defined either declaratively through attributes or programmatically.  Declarative properties use an attribute on public properties to identify configuration properties.  Programmatically declaring configuration properties requires that a configuration property field be created for each attribute.  Declarative properties are easier to write and understand but run slightly slower than programmatic properties.  Otherwise either approach can be used or they can be used together. 

Declaratively

To define a property declaratively do the following.

  1. Declare a public property in the section/element class.
  2. Define a getter and, optionally, a setter for the property.
  3. Add a ConfigurationProperty attribute to the property.

The public property name will generally match the XML attribute but use Pascal casing.  The type will be a standard value type such as bool, int or string.  Use the type most appropriate for the property.

There are no fields to back the properties.  The subsystem is responsible for managing the property values.  The base class defines an indexed operator for the section/element that accepts a string parameter.  The parameter is assumed to be the name of an XML attribute.  The base class will look up the attribute and get or set the value as needed.  The properties are assumed to be objects so casting will be necessary.

The ConfigurationProperty only requires the name of the XML attribute.  There are several optional parameters that can be specified as well.

Parameter Default Meaning
DefaultValue None If the attribute is not specified then the corresponding property will have the specified value.
IsDefaultCollection False Used for collections.
IsKey False Used for collections.
IsRequired False True to specify that the attribute is required.
Options None Additional options.

The DefaultValue parameter should be used to give a property a default value.  Since the base class manages property values rather than using fields it is not necessary to worry about this parameter in code.

The IsRequired parameter specifies that the attribute must be specified.  If it is not then an exception will occur.  This parameter should be used for properties that can have no reasonable default value.  The IsRequired parameter for the attribute does not work when applied to a child element.  The subsystem will automatically create a new instance of any child elements when it reflects across the configuration properties.  Later when the subsystem tries to verify that all required properties have received a value it cannot tell a difference between default initialized elements and those that were contained in the configuration file.  To use the IsRequired parameter with a child element you must programmatically declare the property instead.

Here is the modified TestsSection class with the configuration properties declaratively defined.  After modifying the code run it and verify the attribute values are correct.  Try removing each attribute from the example XML and see what happens.

public class TestsSection : ConfigurationSection 

    [ConfigurationProperty(“logging”, DefaultValue=false)] 
    public bool Logging 
    { 
        get { return (bool)this[“logging”]; } 
        set { this[“logging”] = value; } 
    } 

    [ConfigurationProperty(“version”, IsRequired=true)] 
    public string Version 
    { 
        get { return this[“version”as string; } 
        set { this[“version”] = value; } 
    } 

 

Programmatically

To define a property programmatically do the following.

  1. Create a field for each attribute of type ConfigurationProperty.
  2. Add each field to the Properties collection of the base class.
  3. Declare a public property for each attribute.
  4. Define a getter and, optional, a setter for each property.

The configuration field that is created for each attribute is used in lieu of an attribute.  The constructor accepts basically the same set of parameters.  Once the fields have been created they must be associated with the section/element.  The configuration properties associated with a section/element are stored in the Properties collection of the base class.  When using declarative programming the properties are added automatically.  In the programmatic approach this must be done manually.  The properties cannot be changed once created so it is best to add the configuration  properties to the collection in the constructor.

A public property for each attribute is created in a similar manner as with declarative programming. The only difference is the lack of an attribute on the property.  Since the configuration property is a field in the class you can use the field rather than the property name if desired.

Here is a modified TestsSection class using the programmatic approach. 

public class TestsSection : ConfigurationSection 

    public TestsSection () 
    { 
        Properties.Add(m_propLogging); 
        Properties.Add(m_propVersion); 
    } 

    public bool Logging 
    { 
        get { return (bool)this[m_propLogging]; } 
        set { this[m_propLogging] = value; } 
    } 

    public string Version 
    { 
        get { return (string)this[m_propVersion]; } 
        set { this[m_propVersion] = value; } 
    } 

    private ConfigurationProperty m_propLogging = 
        new
 ConfigurationProperty(“logging”typeof(bool), false);
    private ConfigurationProperty m_propVersion = 
        new
 ConfigurationProperty(“version”typeof(string), 
               null, ConfigurationPropertyOptions .IsRequired); 
}

The programmatic approach can be optimized by using static fields and static constructors.  But that requires more advanced changes so we won’t cover that today.

Validation

The subsystem will ensure that an XML attribute can be converted to the type of the property.  If it cannot then an exception will occur.  There are times though that you want to do even more validation.  For example you might want to ensure a string is in a certain format or that a number is within a certain range.  For that you can apply the validator attribute to the property as well.  There are several different validators available and you can define your own.  The following table defines some of them.

Type Description
CallbackValidator Calls a method for more complex validation.
IntegerValidator Validates a numeric value including range and precision.
LongValidator Same As IntegerValidator but applies to longs.  (Notice that MS failed to follow the naming guidelines for this type :})
PositiveTimeSpanValidator Validates a time duration.
RegexStringValidator Validates a string against a regular expression.
StringValidator Validates a string for length and content.
SubclassTypeValidator Validates the type of a value.
TimeSpanValidator Validates a time duration.

The type name given is the name of the underlying validator class that is used.  You can, if you like, create an instance of the type and do the validation manually.  More likely though you’ll apply the attribute of the same name instead.  Here is a modified version of the version attribute to ensure that it is of the form x.y.

[ConfigurationProperty(“version”, IsRequired = true
[RegexStringValidator(@”(d+(.d+)?)?”)] 
public string Version 

   get { return this[“version”as string; } 
   set { this[“version”] = value; } 
}

If you are good with regular expressions you might have noticed that the expression allows for an empty string.  This is a peculiarity of the subsystem.  It will call the (at least the Regex) validator twice.  The first time it passes an empty string.  The validator must treat an empty string as valid otherwise an exception will occur.

Child Elements

Now that we can define sections and properties all we have to do is add support for child elements and we’re done.  A child element, as already mentioned, is nothing more than a configuration element.  In fact all that is needed to support child elements is a new configuration element class with the child configuration properties.  Remember that a configuration section is just a top-level configuration element.  Everything we have discussed up to now applies to configuration elements as well.

Here is the declaration for the configuration element to back the test XML element.  The properties are included.

public class TestElement : ConfigurationElement 

   [ConfigurationProperty(“async”, DefaultValue=false)] 
   public bool Async 
   { 
      get { return (bool)this[“async”]; } 
      set { this[“async”] = value; } 
   } 

   [ConfigurationProperty(“failureAction”, DefaultValue=“Continue”)] 
   public FailureAction FailureAction 
   { 
      get { return (FailureAction)this[“failureAction”]; } 
      set { this[“failureAction”] = value; } 
   } 

   [ConfigurationProperty(“name”, IsKey=true, IsRequired=true)] 
   public string Name 
   { 
      get { return this[“name”as string; } 
      set { this[“name”] = value; } 
   } 

   [ConfigurationProperty(“timeOut”, DefaultValue=120)] 
   [IntegerValidator(MinValue=0, MaxValue=300)]
   public int TimeOut 
   { 
      get { return (int)this[“timeOut”]; } 
      set { this[“timeOut”] = value; } 
   } 

   [ConfigurationProperty(“type”, IsRequired = true)] 
   public string Type 
   { 
      get { return this[“type”as string; } 
      set { this[“type”] = value; } 
   } 
}

public enum FailureAction { Continue = 0, Abort, }

A couple of things to note.  FailureAction is actually an enumeration.  This is perfectly valid.  The only restriction is that the attribute value must be a, properly cased, member of the enumeration.  Numbers are not allowed. 

The second thing to note is the IsKey parameter applied to Name.  Only one property can have this parameter set.  It is used to uniquely identify the element within a collection of elements.  We will discuss it shortly.

The configuration section needs to be modified to expose the element as a child.  Here is the modified class definition.

public class TestsSection : ConfigurationSection 

   …

   [ConfigurationProperty(“test”)] 
   public TestElement Test 
   { 
       get
 { return this[“test”as TestElement; } 
   } 
}

Notice that we did not add a setter here since the user will not be adding the test explicitly.  Modify the example XML to include (only) one of the elements from the target XML.  Comment out any parameter elements for now.  Compile and run the code to confirm everything is working properly.

As an exercise try creating the parameter child element yourself.  Modify the example XML to include (only) one of the elements from the target XML and verify it is working properly.  Do not forget to update the TestElement class to support the parameter.

Collections

The final piece of the configuration puzzle is collections.  To support a collection of elements a configuration collection class must be created.  This collection is technically just a class deriving from ConfigurationElementCollection.  What makes collections so frustrating is that there are quite a few things that have to be done to use them in a friendly manner.  Add to that confusing documentation and incorrect examples and it is easy to see why people are confused.

To keep things simple for now we are going to temporarily modify our example XML to fall in line with the default collection behavior.  We will then slowly morph it into what we want.  We will add support for multiple tests in the section first.  Here are the steps for adding a collection with default behavior to a section/element.

  1. Create a new collection class deriving from ConfigurationElementCollection.
  2. Add a ConfigurationCollection attribute to the class.
  3. Override the CreateNewElement method to create an instance of the appropriate type.
  4. Override the GetElementKey method to return the key property of an element.
  5. In the parent element modify the public property to return an instance of the collection type.

Here is boilerplate code for an element collection.  In fact you can create a generic base class if you want.  You’ll see why that is a good idea later.

[ConfigurationCollection(typeof(TestElement))] 
public class TestElementCollection : ConfigurationElementCollection 

   protected override ConfigurationElement CreateNewElement () 
   { return new TestElement(); } 

   protected override object GetElementKey ( 
           ConfigurationElement element ) 
   { return ((TestElement)element).Name; } 
}

CreateNewElement is called when the subsystem wants to add a new element to the collection as it is parsing.  The GetElementKey method is used to map an element to a unique key.  This is where the IsKey parameter comes in.  These are the only methods that have to be implemented but it is generally advisable to add additional methods for adding, finding and removing elements if the collection can be written to in code.

Now that the collection is defined it needs to be hooked up to the parent element.  Here is the updated TestsSection class property definition.  Notice that the property name and the XML element name were changed to clarify that it is a collection.

[ConfigurationProperty(“tests”)] 
public TestElementCollection Tests 

    get 
    { 
        return this[[“tests”as TestElementCollection; 
    } 
}

As an aside the ConfigurationCollection element can be applied to the public property in the parent class rather than on the collection type itself.  This might be useful when a single collection type can be used in several different situations.  In general though apply the attribute to the collection type.

The final changes we need to make are to the example XML itself.  The collection of tests need to be contained in a child element rather than directly in the section because that is how the collection is defined.  Additionally the default collection behavior is to treat the collection as a dictionary where each element maps to a unique key.  Elements in the collection can be added or removed or the entire collection cleared using the XML elements: add, remove and clear; respectively.  Here is the (temporary) updated example XML fragment.

<tests version=”1.0” logging=”True“>
    <tests>
        <add name=”VerifyWebServer” 
             type
=”TestFramework.Tests.ServerAvailable” 
             failureAction
=”Abort“>
            <parameter name=”url” value=”http:\www.myserver.com“>
        </add>
        <add name=”CheckService1” 
             type
=”TestFramework.Tests.WebServiceInvoke” 
             async
=”true” timeOut=”120“>
            <parameter name=”url” 
                 value
=”http:\www.myserver.comservice1.asmx” />
            <!–<parameter name=”parameter_1” value=”hello” />
              <parameter name=”returns” value=”HELLO” />–>                       
        </add>
    </tests>
</tests>

 

Altering Nameses

The first thing that you will likely want to change is the name used to add new items to the collection.  It is standard to use the element name when adding new items.  To change the element name for adding, removing and clearing items use the optional parameters on the ConfigurationCollection

[ConfigurationCollection(typeof(TestElement), AddItemName=“test”)] 
public class TestElementCollection : ConfigurationElementCollection
{
   …
}

 

<tests version=”1.0” logging=”True“>
    <tests>
        <test name=”VerifyWebServer” 
              type
=”TestFramework.Tests.ServerAvailable” failureAction=”Abort“>
            <parameter name=”url” value=”http:\www.myserver.com” />                  
        </test>
        …
           
    </tests>
</tests>

 

Default Collectionon

Having a parent element for a collection is necessary when you are dealing with multiple collections inside a single parent element.  Normally however this is not the case.  You can eliminate the need for an element around the collection children by using the default collection option on the configuration property.

Modify the configuration property that represents the default collection to include the IsDefaultCollection parameter.  Set the name of the property to an empty string.  If this is not done then the subsystem will fail the request.  During parsing any element that is found that does not match an existing configuration property will automatically be treated as a child of the default collection.  There can be only one default collection per element.

Here is the TestsSection modified to have Tests be the default collection.  The example XML follows.

public class TestsSection : ConfigurationSection 

   …

   [ConfigurationProperty(“”, IsDefaultCollection=true)] 
   public TestElementCollection Tests 
   { 
      get { return this[“”as TestElementCollection; } 
   } 
}

 

<tests version=”1.0” logging=”True“>
    <test name=”VerifyWebServer” 
          type
=”TestFramework.Tests.ServerAvailable” 
          failureAction
=”Abort“>
        <!–<parameter name=”url” value=”http:\www.myserver.com” />–>           
    </test>
    <test name=”CheckService1” 
          type
=”TestFramework.Tests.WebServiceInvoke” 
          async
=”true” timeOut=”120“>
        <!–<parameter name=”url” value=”http:\www.myserver.comservice1.asmx” /> 
        <parameter name=”parameter_1” value=”hello” /> 
        <parameter name=”returns” value=”HELLO” />–>            
    </test>
</tests>  

 

Collection Options

The default collection type allows for elements to be added, removed or the entire list cleared.  This is often not what is desired.  The alternative collection type allows for new elements to be added only.  To tell the subsystem that the collection should not allow changes to the existing elements and to allow only new elements it is necessary to overload a couple properties in the collection class.   

The CollectionType property specifies the type of the collection being used.  The default is AddRemoveClearMap which specifies a modifiable collection.  The alternative is BasicMap which allows only additions.  In the case of a basic map the add, remove and clear item names are not used.  Instead it is necessary to override the ElementName property to specify the name of child elements.

[ConfigurationCollection(typeof(TestElement))] 
public class TestElementCollection : ConfigurationElementCollection 

   protected override string ElementName 
   { 
      get { return “test”; } 
   } 

   public override ConfigurationElementCollectionType CollectionType 
   { 
      get { return ConfigurationElementCollectionType.BasicMap; } 
   }
   …
}

A warning about basic maps.  The collection type is a parameter to the ConfigurationCollection attribute.  However it does not appear to work properly when using a basic map.  Stick with overriding the property instead.

Now that you have seen how to add support for collections try updating the TestElement class to support multiple parameters using a default basic map collection.  At this point everything has been covered to create the code to read the target XML from the beginning of the article.

Updating Configurations

One of the features added in v2.x of the configuration subsystem was the ability to modify and save the configuration data.  While application configurations should remain read-only (for security purposes), user configuration files can be modified.  The example code is going to be modified to allow new tests to be added and saved.

To support modification of a configuration section/element the properties must support setters.  In our example code we made all the properties settable so we do not need to make any changes.  Some properties can have setters and others not.  It is all dependent upon what the configuration section needs to support.  The exception to the rule is collections.  By default a collection does not expose any methods to modify the collection elements.  It is necessary to manually add the appropriate methods if configuration collections can be modified.  Additionally the property IsReadOnly method must be overloaded to allow modifying the collection.

The following modifications need to be made to the test collection to support adding new tests, removing existing tests and clearing the collection.

[ConfigurationCollection(typeof(TestElement))] 
public class TestElementCollection : ConfigurationElementCollection 
{
   …
   
   public override bool IsReadOnly () 
   { return false; } 

   public void Add ( TestElement element ) 
   { BaseAdd(element); } 

   public void Clear () 
   { BaseClear(); } 

   public void Remove ( TestElement element ) 
   { BaseRemove(element.Name); } 

   public void Remove ( string name ) 
   { BaseRemove(name); } 
}

For test purposes the engine will be modified to generate a new test section (with a dummy test) if none can be found in the configuration file.

public void LoadTests () 

   //1 – Try and get the section
   m_Section = ConfigurationManager.GetSection(“tests”)
                   as TestsSection; 
   if ((m_Section == null) || 
       !m_Section.ElementInformation.IsPresent) 
   { 
      //2 – Open the configuration file 
      System.Configuration.Configuration cfg = 
            ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); 

      //3 – Create the section if necessary
      if (cfg.Sections[“tests”] == null
         cfg.Sections.Add(“tests”new TestsSection()); 
      m_Section = cfg.GetSection(“tests”as TestsSection; 

      //4 – Add a dummy test 
      TestElement test = new TestElement(); 
      test.Name = “Dummy Test”
      test.Type = “DummyTest”
      m_Section.Tests.Add(test); 

      //5 – Save the changes 
      m_Section.SectionInformation.ForceSave = true
      cfg.Save(); 
   }; 
}

Let’s walk through the code.  The engine first tries to get the section (1).  If it fails to get the section then it will create a new one.  The configuration subsystem (contrary to documentation) seems to always return an instance of the section handler even if the actual section does not exist in the file.  The example code checks to determine if the section actually exists or not.

The subsystem uses the Configuration class (not the namespace) to represent a configuration file.  ConfigurationManager maintains an instance internally for the application configuration but this field is not exposed.  Instead it is necessary to explicitly open the configuration file and modify it.  Earlier it was mentioned that the data is only parsed once and that remains true.  However multiple instances of the section class are returned.  Changes made in one instance of a section are not visible in another. 

The engine next (2) opens the configuration file explicitly.  The engine then (3) creates a new section in the off chance that it did not exist yet.  Now the engine (4) creates a dummy test and adds it to the section.  Finally (5) the updated section is saved back to disk.

Temporarily comment out the tests element in the XML file and run the code.  Look at the XML file and confirm the new test was created.  What! It wasn’t?  Actually it was.  The problem is that the debugger is getting in the way.  By default the vshost process is used to run the program.  As a result the actual configuration file is <app>.vshost.exe.config.  Additionally this file is overwritten when debugging starts and ends.  Hence you are likely to miss the change.  Place a breakpoint at the end of the LoadTests method and run it again.  Now examine the configuration file to confirm the changes were made.

There are many more things that can be done to update the configuration file.  You can save the file elsewhere, save only some changes or even modify other files.  The preceding discussion should be sufficient to get you started though.

Dynamic Sections

The configuration subsystem is based upon deterministic parsing.  At any point if the subsystem cannot match an XML element/attribute to a configuration element/property it will throw an exception.  Configuration elements/sections expose two overridable methods (OnDeserializeUnrecognizedAttribute and OnDeserializeUnrecognizedElement) that are called if the parse finds an unknown element/attribute during parsing.  These methods can be used to support simple dynamic parsing.

For unknown attributes the method gets the name and value that was parsed.  If the method returns true then the subsystem assumes the attribute was handled otherwise an exception is thrown.  The following method (added to TestElement) silently ignores a legacy attribute applied to a test.  Notice that the element is compared using case sensitivity.  Since XML is case sensitive comparisons should be as well.

protected override bool OnDeserializeUnrecognizedAttribute ( 
         string
 name, string value ) 

   //Ignore legacy baseType attribute 
   if (String.Compare(name, “baseType”
           StringComparison.Ordinal) == 0
      return true

   return base.OnDeserializeUnrecognizedAttribute(name, value); 
}

For unknown elements the method must parse the XML manually and return true to avoid an exception.  The important thing to remember about this method is that all child elements must be parsed otherwise the subsystem will not recognize the element and call the method again.  The following method (added to TestElement) silently ignores a legacy child element that contained some initialization logic.  In this particular case the child elements are not important (or parsed) so they are skipped.

protected override bool OnDeserializeUnrecognizedElement ( 
           string
 elementName, System.Xml.XmlReader reader ) 

   //Ignore legacy initialize element and all its children 
   if (String.Compare(elementName, “initialize”
           StringComparison.Ordinal) == 0
   { 
      reader.Skip(); 
      return true
   }; 
  
   return base.OnDeserializeUnrecognizedElement(elementName, reader); 
}

A word of caution is in order when using collections.  If a collection’s item name properties have been modified (for example from add to test) then the method is called for each item.  The underlying collection overrides this method to handle the item name overrides.  Therefore do not assume that just because this method is called a truly unknown element has been found.

Before getting any wild ideas of how to get around the subsystem’s restrictions on element contents be aware that you cannot use the above methods to parse certain XML elements including CDECLs and element text.  These XML entities will always cause the subsystem to throw an exception.

Standard Sections

The v1.x subsystem supported several standard section types that continue to be useful.  They allow for storing custom settings without creating a custom section handler. The only downside is that they cannot be configured. 

DictionarySectionHandler can be used to store a set of key-value pairs in a section.  The following example demonstrates such a section.

<configSections>
         <section name=”settings” 
                  type
=”System.Configuration.DictionarySectionHandler” />
   </configSections>

   <settings>
         <add key=”Setting1” value=”1” />
         <add key=”Setting2” value=”2” />
         <add key=”Setting3” value=”3” />
   </settings>

Here is how it would be used.  Notice that the return value is Hashtable rather than a section handler instance.

Hashtable settings = ConfigurationManager.GetSection(“settings”as Hashtable;

The NameValueSectionHandler works identically to DictionarySectionHandler except the returned value is NameValueCollection.

The SingleTagSectionHandler is used to store a single element with attribute-value pairs.  The returned value is a Hashtable where the attribute names are the keys.

The three legacy section handlers can be used in lieu of creating custom section handlers when simple dictionaries or attribute-value pairs are needed.  As a tradeoff they do not support any of the advanced functionality of the subsystem including modification, validation or default values.

Implementing IDisposable

This article will attempt to explain what the IDisposable is, when it should implement it and how to implement it properly.

Why Do We Need It

Anyone who has worked with .NET for any length of time knows that .NET uses automatic memory management.  The garbage collector (GC) runs in the background and will free up memory as needed.  This is a non-deterministic approach meaning there is little control over when it occurs.

In general this is a good thing.  Does it really matter whether or not when memory gets freed?  In general, no.  However some objects really must have deterministic behavior.  The canoncial example would be any class that uses unmanaged, shared resources like database connections, file handles or synchronization objects.  In these cases it is important to be able to free these objects when they are no longer needed.

Ideally the framework would detect that an object is no longer needed as soon as it occurs and automatically free up the memory.  However this would put an undo strain on the system.  Therefore for deterministic clean up it is still necessary for developers to manually free objects when they are no longer needed.  So how does a developer know when they should free an object or let .NET handle it.  Enter IDisposable.  This interface identifies a type that must be freed when it is no longer needed.  Of course there is always the chance that the user will forget to free the object so .NET still has to ensure that the object gets freed at some future point.

IDisposable

IDisposable has only a single member called Dispose.  This method is called when the object needs to be freed.  Internally .NET will always call this method when the object is freed.  However users should also call this method when the object is no longer needed.  Within this method any shared/unmanaged resources should be released.  Here is an example of file-type class’es implementation of the interface.

public class FileBase : IDisposable 

   private IntPtr m_pUnmanagedResource; 

   public void Dispose () 
   { 
      if (m_pUnmanagedResource != IntPtr.Zero) 
      { 
         //Free it 
         m_pUnmanagedResource = IntPtr.Zero; 
      }; 
   } 
}

This implementation uses implicit interface implementation support to expose a public Dispose method that clients can call to clean up the resource.  Dispose does not necessarily mean much to a caller so a separate method can be created that internally does the clean up and then explicitly implement the interface like so.

public class FileBase : IDisposable 

   private IntPtr m_pUnmanagedResource; 

   public void Close () 
   { 
      if (m_pUnmanagedResource != IntPtr.Zero) 
      { 
         //Free it 
         m_pUnmanagedResource = IntPtr.Zero; 
      }; 
   } 

   void IDisposable.Dispose () 
   { Close(); } 
}

Now the act of closing a file is exposed while under the hood the close method is used to dispose of the unmanaged resources.

Ensuring Clean Up

The above code handles the case where the user will remember to clean up the object when they are done but it still does not handle the case of .NET itself cleaning up the object.  In order to run code when an object is to be freed a finalizer is needed for the class.  A finalizer actually delays the process of freeing the object but it allows clean up code to execute.  Whenever IDisposable is implemented it is important to analyze whether a finalizer is also needed (see below for more information).  Whenever a finalizer is defined IDisposable should also be implemented.

For the example class the Close method should be called to clean up the unmanaged resource.

public class FileBase : IDisposable 

   ~FileBase () 
   { 
      Close(); 
   } 

   private IntPtr m_pUnmanagedResource; 

   public void Close () 
   { 
      if (m_pUnmanagedResource != IntPtr.Zero) 
      { 
         //Free it 
         m_pUnmanagedResource = IntPtr.Zero; 
      }; 
   } 

   void IDisposable.Dispose () 
   { Close(); } 
}

The above code ensures that the file is closed whether the user does it manually or not but there is a problem.  GC is non-deterministic for all objects.  Any reference fields within the class might or might not have already been freed by the time the finalizer is called.  When the finalizer is called the code cannot refer to any reference fields within the class.  A method is needed to tell the Close method not to refer to these fields.  The defacto method is to define a private (or protected) method called Dispose that accepts a boolean argument indicating whether the object is being disposed (i.e manually) or not (being invoked through GC).  Within this helper method is where the actual clean up work is done.

public class FileBase : IDisposable 

   ~FileBase () 
   { 
      Dispose(false); 
   } 

   private IntPtr m_pUnmanagedResource; 

   public void Close () 
   { 
      Dispose(true); 
   } 

   private void Dispose ( bool disposing ) 
   { 
      if (disposing) 
      { 
         //We can access reference fields in here only 
      }; 

      //Only value fields and unmanaged fields are  
      //accessible from this point on 
      if (m_pUnmanagedResource != IntPtr.Zero) 
      { 
         //Free it 
         m_pUnmanagedResource = IntPtr.Zero; 
      }; 
   } 

   void IDisposable.Dispose () 
   { Close(); } 
}

The above code works but is suboptimal in, what is hoped, the common case of a client explicitly freeing the object.  If the client calls Close then the object does not need to be finalized anymore.  What is needed is a way to tell .NET not to call the finalizer if the object has been disposed.  This requires a one line addition to the Close method (or whatever method is the explicit cleanup method).

public void Close () 

   Dispose(true); 
   GC.SuppressFinalize(this); 
}

The GC.SuppressFinalize method tells .NET not to call the finalizer for the object specified as a parameter.  Since the object has already been cleaned up there is no benefit in calling it anyway.  This is only needed for classes with finalizers. 

This completes the implementation of IDisposable.

Using 

C# and VB.NET both support the using statement.  This statement should be used whenever dealing with IDisposable objects.  The statement ensures that the object is explicitly disposed when it goes out of scope.  Since this is the best behavior it should be used it in almost all cases.  Here is an example of using the statement.

public string ReadFile ( string fileName ) 

   using(File file = new File(fileName)) 
   { 
      … 
   }; 
}

When the File object goes out of scope at the end of the using statement the IDisposable.Dispose method will be automatically called.  In the example code it will internally call Close which calls Dispose(true) to clean up the unmanaged resources.  Even if an exception occurs the object will be disposed.

In the few cases where using can not be used then use a try-finally block instead, like so.

public string ReadFile ( string fileName ) 

   File file = null

    try 
   { 
      … 
   } finally 
   { 
      if (file != null
         file.Close(); 
   }; 
}

This is not as clean as using but it works.

When To Implement

The IDisposable interface should only be implemented when it is needed.  Here are the common cases where it should be implemented. 

  1. When a type contains an unmanaged or shared resource it should implement the interface and a finalizer.
  2. When a type contains fields that implement IDisposable then the type should implement the interface but it SHOULD NOT implement a finalizer.
  3. When a type uses a lot of memory internally it should consider implementing IDisposable but it SHOULD NOT implement a finalizer.

Caveats

Finally here are some caveats about the interface.

  1. An object can be disposed multiple times.  Therefore the dispose method must handle this case.
  2. When the GC calls a finalizer it will be on an arbitrary thread.  Do not access any thread-specific values.
  3. The GC runs all finalizers on the same thread and there is no exception handling so the dispose method should not throw exceptions nor deadlock.
  4. Some objects can support resurrection (meaning they are disposed and then recreated).  This is difficult to do properly and should be avoided.  Assume that a disposed object is permanently gone.
  5. The dispose method can be called on multiple threads so thread-safety should be taken into account.
  6. When called from the finalizer the dispose method can not reference any reference fields.

Access Control in .NET

(Originally published: 30 Mar 2008)

As of v2 of .NET you can get and set the security of securable objects such as files, folders and registry keys.  Other object types can be added as needed.  While all the access control objects derive from the same base classes they share only a few common elements.  Therefore working with the security of a file requires using different classes than registry keys.  This is an unfortunate limitation of the existing implementation since a generic security handler can not be created with the existing objects.  This article will discuss working with the access control classes in .NET.  While the article will use folders as the target object the concepts (but not the classes) apply to any securable object.  To provide a graphical representation for this discussion open the properties for a folder in Windows Explorer and go to the Security tab.  Click the Advanced button to gain access to the more advanced features we will discuss.

All the core access control types are contained in the System.Security.AccessControl namespace.  The types used to get user/group information are available in System.Security.Principal.

Security Basics

Before we get into the .NET implementation it is important to clarify a few security-related concepts.

Object Rights

Each securable object has a list of operations that it supports.  This list of operations are known as security rights (or just rights).  Rights are generally represented as a set of bit flags internally.  Rights can be combined to form more complex rights.  For example Full Control is a combination of all rights.  Certain rights are shared by all objects including read and modify. 

Due to the variety of rights available, security rights are not exposed directly by the base security classes.  Instead each object-dependent implementation exposes its own set of rights as an enumeration.  We will discuss these rights later.

Identity References

In Windows both users and groups (known as identities) are represented as globally unique values known as SIDs (security identifiers).  SIDs are well-formed but not easily remembered.  All identities are referenced via SIDs internally.  Part of a SID includes the domain or machine that owns the identity.  All SIDs from the same machine/domain share at least a partial identifier.

In .NET the SecurityIdentifier class is used to wrap a SID.  While useful for uniquely identifying a user or group creating an instance of this class directly is rarely done.  Instead this class is generally returned by security-related methods.

Since working with SIDs is not generally useful, other than for uniqueness, .NET provides a more user-friendly version available called NTAccount.  This class is specifically used for users and groups.  It provides the user-friendly name for a SID.  You can easily convert between NTAccount and SecurityIdentifier using the Translate method.  In the few cases where NTAccount can not map the SID to a friendly name it will simply use the SID.  The following example displays the name and SID of the current user.

public void DisplayUser () 

   WindowsIdentity id = WindowsIdentity.GetCurrent(); 

   string name = id.User.Translate(typeof(NTAccount)).Value; 
   string sid = id.User.Translate(typeof(SecurityIdentifier)).Value; 

   Console.WriteLine(“Current User = {0}, [{1}]”, name, sid); 
}

Both NTAccount and SecurityIdentifier derive from IdentityReference.  The base class is generally used in the various security calls to allow either class to be used.  You will almost always want to use NTAccount.

DACLs and SACLs

In Windows there are discretionary access control lists (DACLs) and system access control lists (SACLs).  DACLs specify the rights assigned to identities on an object.  Each entry in the DACL is known as a ACE.  SACLs determine the auditing done on an object.  In most cases you will be working with DACLs. 

In .NET DACLs are known as access rules and SACLs are known as audit rules.  Each access rule contains a security right and the identity that has (or does not have) the right.  .NET uses the abstract class AccessRule to represent an access rule.  Each audit rule contains a security right and the condition under which it is logged (success or failure).  .NET uses the abstract class AuditRule to represent an audit rule.  Neither of these classes are used in most cases as the derived classes provide more information.

In Windows an access rule can either allow or deny a specific right to an identity.  Most rules allow the right.  In the rare case of a deny right it takes precedence over all access rules.  In fact access rules are always listed such that deny rules come first.  A deny rule always overrides an allow rule.  Therefore Windows (and your code) can stop looking for a right as soon as it sees a deny rule for the right.

Object Security

Each securable object has a set of security properties exposed through an ObjectSecurity-derived class.  The derived class exposes methods to access all the security settings of an object including the access and audit rules and the owner.  It is also through this class that we can modify the security of an object.  Unfortunately the base ObjectSecurity class does not expose any of these methods.  This makes working with securable objects in a generic manner difficult.  Instead the base class exposes properties that define the types used to represent the access and audit rules, discussed later. 

The following table defines some common securable objects and their associated object security type.

Object Type ObjectSecurity Class Accessor Class
Active Directory ActiveDirectorySecurity DirectoryEntry
Directory DirectorySecurity Directory, DirectoryInfo
File FileSecurity File, FileInfo
Registry Key RegistrySecurity RegistryKey

Fortunately all the built in classes expose the same set of methods so, other than the type name, working with each type is the same.  There is another solution to this delimma.  Most of the security classes derive from CommonObjectSecurity (which itself derives from ObjectSecurity).  This base class exposes the core methods that we will discuss later.  Therefore you can use CommonObjectSecurity in cases where you might want to work with different object types.  Remember that not all security classes derive from this base class.  ActiveDirectorySecurity is one such case.

So how do you get the object’s security to begin with?  As with the security classes, there is no generic way to get this information.  In the above table the Accessor Class column identifies one or more classes that can be used to get access to an object’s security.  In all cases except Active Directory, the type(s) expose a GetAccessControl method.  Static classes return the object security for the parameter passed to the method.  Instance classes return the object security for the current instance.

The following example gets the security for the C:Windows directory.

DirectorySecurity sec = Directory.GetAccessControl(@”c:Windows”);

 

Access Rules

Each access rule represents: a right, allow/deny flag and the associated identity.  For the standard objects a custom enumerated type is defined to identify the security rights available.  The type of the enumeration is available through the AccessRightType property on the object’s security class, if desired.  The following table defines the enumeration for the standard object types.  We will discuss the last two columns later.

Object Type AccessRightType AccessRuleType AuditRuleType
Active Directory ActiveDirectoryRights ActiveDirectoryAccessRule ActiveDirectoryAuditRule
Directory FileSystemRights FileSystemAccessRule FileSystemAuditRule
File FileSystemRights FileSystemAccessRule FileSystemAuditRule
Registry Key RegistryRights RegistryAccessRule RegistryAuditRule

Starting to notice a pattern yet.  The access right enumerations all end in -Rights.  All the enumerations are marked as flags because several of the rights are combinations of other rights (such as full control).  To get the access rules associated with an object use the GetAccessRules method.  The following example gets the access rules for a directory.

public void PrintDirectorySecurity ( string path ) 

   DirectorySecurity sec = Directory.GetAccessControl(path); 

   … 
}

The GetAccessRules method accepts three parameters: include explicit, include inherited and type of identity.  The first parameter specifies whether rights explicitly assigned to an object are returned.  This is almost always the desired case.  The second parameter specifies where rights inherited from the object’s parent are returned.  The final parameter determines the type of identity reference to be returned.  As discussed earlier there are two standard types: NTAccount and SecurityIdentifier.  For user interfaces the NTAccount is the general choice.

Once we have the access rules we can enumerate them.  The following table lists the important properties of each rule.

Property Description
AccessControlType Determines if this is an allowed or denied right
IdentityReference The user/group with the right
InheritanceFlags Controls how the right is inherited by children
IsInherited Determines if this is an explicit or inherited right
PropagationFlags Determines how the right propagates to children

Notice that the actual rights are not part of the access rule, at least not directly.  Remember that CommonObjectSecurity provides a generic implementation of the security.  However the actual rights are enumerations defined for each object type.  Since CommonObjectSecurity has no way to know what the enumerated values are it doesn’t expose them as a strongly typed property.  The AccessMask property can be used to get the underlying bitmask.  Fortunately each AccessRule-derived class exposes the rights as a strongly typed property named after the enumerated type.  This is the preferred method for getting the rights. 

The following code will list all the rights associated with an object along with some other property values.

public void PrintDirectorySecurity ( string path ) 

   Console.WriteLine(String.Format(“{0,-30} Allowed Inherited {1,-15}”
                            “User/Group”“Right”)); 
   Console.WriteLine(new string(‘-‘70)); 

   DirectorySecurity sec = Directory.GetAccessControl(path); 
   foreach (FileSystemAccessRule rule in 
                sec.GetAccessRules(truetruetypeof(NTAccount))) 
   { 
      Console.WriteLine(“{0,-30} {2} {3} {1,-15:g}”
            rule.IdentityReference, 
            rule.FileSystemRights, 
            rule.AccessControlType == AccessControlType.Allow, 
            rule.IsInherited); 
   }; 
}

There are a couple of important points about enumerating access rules.  Firstly the rights are not always a valid combination of flags from the enumeration.  Secondly an identity can appear more than once in the list.  This can occur for a variety of reasons, inheritance being one of the more common.  Therefore if you want to get all the rights owned by an identity you need to enumerate all rules.  Finally remember that there are both allow and deny rules.  Deny rules come before allow rules and take precedence.

The following method is a simple implementation for getting the rights of a specific identity.  It takes the associated group memberships into account and deny rights.

static FileSystemRights GetObjectRights (
   DirectorySecurity security,
   WindowsIdentity id ) 

   FileSystemRights allowedRights = 0
   FileSystemRights deniedRights = 0

   foreach (FileSystemAccessRule rule in 
              security.GetAccessRules(truetrue, id.User.GetType())) 
   { 
      //If the identity associated with the rule        
        //matches the user or any of their groups  
      if (rule.IdentityReference.Equals(id) ||            
             id.Groups.Contains(rule.IdentityReference)) 
      {
            uint right = (uint)rule.FileSystemRights & 0x00FFFFFF;

         //Filter out the generic rights so we get a           
            //nice enumerated value  
         if (rule.AccessControlType == AccessControlType.Allow) 
            allowedRights |= (FileSystemRights)(right); 
         else 
            deniedRights |= (FileSystemRights)(right); 
      }; 
   }; 

   return allowedRights ^ deniedRights; 
}

The method basically enumerates the access rules of the object and builds up the list of rights for the user (taking their group membership into account).  Denied rights are tracked separately.  After all the rights are determined the allowed rights are returned with any denied rights removed.  Notice the filtering that is going on when adding the rights to the list.  The filter removes the extra bits and generic rights that might be associated with a rule.  These are not supported by the various Rights enumerations and would cause us problems if we wanted to see the string representation.

In the Advanced Security Settings of a folder in Windows Explorer the Permission entries map to these access rules.  You will find that Explorer collapses some of the rules for convenience.  We will discuss the Apply To column later.

Inheritance and Propagation

You can determine if a rule is inherited through the IsInherited property.  Whether a rule is inherited or not is determined by the InheritanceFlags and PropagationFlags properties.  The inheritance flags determine who inherits the rule: child containers (folders), objects (files), both or neither.  The propagation flags determine whether the object (for which you are adding the rule) gets the rule as well.  The following table defines the various combinations of flags and how they map to folder security in Explorer.

PropagationFlags InheritanceFlags Description (Explorer)
None ContainerInherit This folder and subfolders
  ObjectInherit This folder and files
  ContainerInherit | ObjectInherit This folder, subfolders and files
InheritOnly ContainerInherit Subfolders only
  ObjectInherit Files only
  ContainerInherit | ObjectInherit Subfolders and files only

The table left out the propagation flag NoPropagationInherit.  This odd flag can be combined with any of the other entries in the table.  When applied it identifies the rule as applying only to the objects and containers within the target object.  In Explorer this maps to the checkbox below the permission entries (when editing) that says Apply these permissions to objects and/or containers within this container only.

Modifying Access Rules

Modifying access rules can be easy or hard depending on the situation.  To give an identity a new right you create a new access rule and then add it to the object security instance using the AddAccessRule method.  The following example gives the specified user the delete right to a directory.

public void GiveUserDeleteAccess ( string path, WindowsIdentity user ) 

   DirectorySecurity sec = Directory.GetAccessControl(path); 

   FileSystemAccessRule rule = new FileSystemAccessRule(user.User, 
                FileSystemRights.Delete, AccessControlType.Allow); 

   sec.AddAccessRule(rule); 

   Directory.SetAccessControl(path, sec); 
}

Notice the call to SetAccessControl.  The object security instance you obtain from GetAccessControl is a snapshot of the current state of the object.  Changes you make to it do not actually get applied until you call SetAccessControl.  As a result it is when you call SetAccessControl that you are most likely to get an exception such as for unauthorized access or for a missing object.  Whenever you make any changes to an object’s security you must remember to call this method to persist the changes.  In general you will make all the necessary changes before attempting to persist them.

Removing rights is even easier.  When removing rights you have a little more flexibility.  You can remove a specific identity/right rule, all rights for a particular identity or all rights for all users.  To remove a specific identity/right use the RemoveAccessRule.  The following example removes the right we added earlier.

public void TakeUserDeleteAccess ( string path, WindowsIdentity user ) 

   DirectorySecurity sec = Directory.GetAccessControl(path); 

   FileSystemAccessRule rule = new FileSystemAccessRule(
                user.User, FileSystemRights.Delete, 
                AccessControlType.Allow); 

   sec.RemoveAccessRule(rule); 

   Directory.SetAccessControl(path, sec); 
}

To remove all the rights a user has to an object use PurgeAccessRules instead.  It simply requires the identity reference.  You could also the ModifyAccessRule method if you like.  The various methods all basically do the same thing.  In fact methods implemented in derived classes generally call into the base class methods.  Prefer the versions defined in derived classes but do not worry about going out of your way to call them if the base classes are sufficient.

Modifying Inherited Rules  

You can not just modify an inherited rule.  The rule is defined and owned by a parent object.  To modify such a rule you first need to break the inheritance.  When breaking inheritance you have the option of either copying the existing inherited rule or removing it altogether.  In Explorer when you attempt to edit an inherited rule you will notice that it does not allow you to edit the inherited rights.  You can, of course, add new rights. 

Unchecking the Include inheritable permissions from this object’s parent box will display a dialog prompting you to copy or remove the inherited rules.  In .NET you use the SetAccessRuleProtection method on the security object.  The following code demonstrates this method.

DirectorySecurity sec = Directory.GetAccessControl(path); 

//Copy existing inherited rules 
sec.SetAccessRuleProtection(falsetrue); 

//or, remove inherited rules 
sec.SetAccessRuleProtection(falsefalse);

After calling SetAccessRuleProtection you can then modifying all the access rules on the object.

Audit rules  

Audit rules work similar to access rules.  In fact the only real difference is that the members contain -Audit rather than -Access.  Therefore if you want to add a new audit rule use AddAuditRule.  To get the audit rules use GetAuditRules, etc. 

Audit rules have the same properties as well except in lieu of AccessControlType (to define allow and deny rules) they have the AuditFlags property.  Auditing can occur when the operation succeeds, fails or both.  The property is a bitwise flag combination of AuditFlags.  The following table defines the flags.

AuditFlags Description
None No auditing
Success Audit on successful attempts
Failure Audit on failure attempts

Ownership

Each securable object is owned by an identity.  Owning an object gives an identity the special privilege of changing the permissions irrelevant of access rules.  This prevents an owner from being locked out of their own object.

To get the owner of an object use the GetOwner method.  The following example gets the owner of the folder.

public IdentityReference GetDirectoryOwner ( string path ) 

   DirectorySecurity sec = Directory.GetAccessControl(path); 
   return sec.GetOwner(typeof(NTAccount)); 
}

Setting the owner is just as easy using the SetOwner method.  To take ownership of an object requires the right permissions so this method can fail.  The following code makes the Administrators group the owner of the folder.

public void SetDirectoryOwner (     
        string path,     
        IdentityReference newOwner ) 

   DirectorySecurity sec = Directory.GetAccessControl(path); 
             
   sec.SetOwner(newOwner); 
}

 

Summary

There are quite a few features of the .NET access control that we did not cover.  However this article should hopefully get you started in the right direction.