Practical .NET
Processing a File or Folder When It Changes in Your Desktop Apps or Windows Service
When you need to know when a file or folder appears, disappears or is renamed -- in fact, if almost anything at all happens in the file system -- you can have Windows notify your application so that you can take action.
Sometimes you're keenly interested in knowing when a file or folder changes. Rather than constantly polling a folder to see what's different, use the Microsoft .NET Framework FileSystemWatcher to notify you when something's happened to the contents of a folder or to a specific file. Once you know something's happened, you can take action.
One warning: To catch changes to the file, you have to keep monitoring the file or folder you're interested in. You can't, unfortunately, use the FileSystemWatcher to determine if a folder or file has changed since the last time your program was running. As a result, this tool works best in a desktop application or a Windows service.
Deciding What To Monitor
There are three parts to using the FileSystemWatcher: You have to initialize it (tell the FileSystemWatcher what you're interested in), turn it on and provide a method that will execute when something you're interested in happens.
The first step in configuring the watcher is to delcare a variable to hold an instance of the FileSystemWatcher class. If you're going to access the FileSystemWatcher from multiple places in your application, you should declare it as a field (that is, in your class, but outside any method or property). However, if you do all of the object's configuration in a single method you can declare the variable inside the method with its configuration code. A typical declaration looks like this:
Dim fsw as New FileSystemWatcher()
Somewhere in your application (probably in your class's constructor), you'll need to configure the watcher. This consists of specifying up to two things:
- The folder you want to monitor (which must exist) using the watcher's Path property
- The kind of files you want to track in the watcher's Filter property (these files don't have to exist yet)
If you want to monitor all the files in a folder (or are only interested in changes to the folder) you can skip setting the Filter property.
This example will catch changes to any file whose extension begins with "nu" in the C:\NuGet folder:
fsw.Path = "C:\NuGet"
fsw.Filter = "*.nu*"
If you want to be notified about changes to exactly one file, set the filter to a complete file name, without wildcards. If you want to monitor a folder's subdirectories, set the watcher's IncludeSubdirectories property to True (the default is False).
In addition to specifying the file or folder you're interested in, you can also specify what changes you want to be notified about by setting the watcher's NotifyFilter property. For example, to be notified when a file's size changes you would set the NotifyFilter to NotifyFilters.Size. To configure the watcher so that you'll be notified whenever a file is changed, use the NotifyFilters.LastWrite filter, like this:
fsw.NotifyFilter = NotifyFilters.LastWrite
To catch multiple events you can Or multiple filters together. This example not only tracks changes to the file's contents, but also tracks changes to the file's name by Oring together the LastWrite filter and the FileName filter (which catches changes to the file's name):
fsw.NotifyFilter = NotifyFilters.LastWrite Or
NotifyFilters.FileName
If you don't set the NotifyFilter property, you'll get the default setting: You'll be notified of changes to the file itself and changes to either the file name or the folder's name.
The FileSystemWatcher does not, however, actually start watching until you set its EnableRaisingEvents property to true. You'll need to put code like this somewhere in your application to turn that on (this is the code that might go in a different method than the rest of the configuration code and cause you to declare the FileSystemWatcher as a field):
fsw.EnableRaisingEvents = True
Assigning an Action
Your last configuration step is to wire up the method you want to be executed when a change happens that meets your Path, Filter and NotifyFilter conditions.
Here, again, you have a chance to control what you respond to: The FileSystemWatcher raises different events when a file or folder is created, changed, deleted or renamed. In Visual Basic, code that runs a method called FileChange when a file is changed or created would look like this:
AddHandler fsw.Changed, New FileSystemEventHandler(AddressOf FileChanged)
AddHandler fsw.Created, New FileSystemEventHandler(AddressOf FileCreated)
In C#, the code to catch the Delete event would look like this:
fsw.Deleted += new FileSystemEventHandler(FileRemoved);
The method that you wire up to the event must have a signature that follows the standard format for event handlers: The event must accept two parameters with the first parameter declared as type object and the second parameter declared as type FileSystemEventArgs.
A typical event handler's declaration might look like this:
Shared Sub FileChanged(source as object, e as FileSystemEventArgs)
The e parameter has three useful properties:
- ChangeType: An enumerated value indicating what change is being reported.
- Name: The name of the file (without the path).
- FullPath: The full path of the file (including the file's name).
Because my example uses a single method to catch both Created and Changed events, in my method I need to check the e parameter's ChangeType property to determine what, exactly, has happened to my file. The ChangeType property will be set to a value from the WatcherChangeTypes enumeration to indicate the type of the change. Here's some typical code to distinguish between changes:
Select Case e.ChangeType
Case WatcherChangeTypes.Changed
'...code to handle updates to an existing file
Case WatcherChangeTypes.Created
'...code to handle a file being created
End Select
End Sub
Rather than use an event handler, you could also call the watcher's WaitForChanged method. Calling WaitForChanged causes your code to pause until one of the changes you specified occurs (you can specify a timeout value so that you don't wait forever). The method returns a WaitForChangedResult object which reports on the change that took place. While I prefer using the events, the WaitForChangedResult object does include one piece of information not available through your method's e parameter: The WaitForChangedResult object has an OldName property that contains the original name of the file before it was renamed.
Some Warnings and Advice
Even if you're not using a single method to catch multiple events, you might need to use the ChangeType property to ensure that you only process a file once -- what looks like a single change to you can trigger multiple events from the watcher. For example, if you move a file to a new folder you can get multiple Delete and Create events as the file is removed from the source folder and added to the destination folder.
On the other hand, you might not get as many events as you might expect. If a folder with files in it is copied into a folder you're watching, then you only get a single Create event for your folder ... and no events at all for the files inside the copied folder.
Another wrinkle to be aware of is that the FileSystemWatcher‘s events are raised as soon as the change starts. As a result, the change might not be complete when your method starts executing. For example, if you tie your method to the Create event, then your event code will start executing as soon as someone starts to copy a file to the folder you're watching. If you attempt to read the file in that event you might find that the copy operation isn't complete and you won't be able to access the file. If you intend to process a file after a change, you should use the LastWrite filter and the Changed event to ensure your event code isn't called until after the operation is complete.
In addition, if your event code is processing a newly changed file you might need to make sure that you're not locking out some other process that needs to access the file (for example, if the process that just changed the file tries to change it again). In addition, your event code is synchronous: Once your method starts running it will prevent any subsequent events from processing until it finishes processing. You won't lose those subsequent events: If additional changes are made to the file while your method is processing, the related events "stack up" behind your event code and fire, in sequence, after your event code finishes.
Obviously some planning is needed. Personally, I've found that it's considerably easier to monitor processes that always add new files rather than processes that modify existing files. I try to structure processes so that they create a file, write to it, close the file and then, if there's something else to do, create a new file. If that's not possible then you'll have to ensure your application and the process updating the file aren't going to be in conflict.
One other caveat: While the NotifyFilters enumeration includes a LastAccess choice for catching when a file is read, it's turned off by default. You can enable it by making a change in the Windows registry, but the reason it's turned off by default is because it consumes a lot of resources. It's probably a good idea to leave it turned off.
Managing files isn't a trivial task, but if you use the FileSystemWatcher you can at least be sure that you won't miss any changes. After that, though, it's your problem.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.