Accessing The File System From .Net

(by Joe Williams for Blayd Software)

Note: this article relates to the .Net Framework version 2.0, some of the classes and class members referenced in this article were not available in earlier framework versions.

Introduction

The .Net Framework System.IO namespace contains the types that are used when interacting with the file system from .Net code. The System.IO namespace has several sub-namespaces that contain the types required for more specialised file system interaction e.g. Compression, IsolatedStorage, etc. but we are not going to cover these topics in this series of articles so we will only be discussing the use of the System.IO types provided for "basic" drive, directory and file handling.

The name "Directory" and "Folder" both refer to the same type of file system object i.e. a container in which files are stored. The name "Directory" tends to be used to refer specifically to a container that stores files, whilst the name "Folder" tends to be a more generic term for a container that may store files but may also store other items such as printers for example. When communicating with end users it is probably best to use the name "Folder", however, developers still tend to use the name "Directory" when referring to a file system container. The .Net Framework classes that relate to file containers are named Directory and DirectoryInfo, therefore, in this series of articles we will mainly use the name "Directory" when referring to a file container but we may, occasionally, have to use the name "Folder", when referring to "Special Folders", for example.

In this article we are going to introduce and discuss the file system utility and base classes that we will need to use when working with directories and files. We will also provide a summary and link to three other articles, each of which provides a detailed discussion of and sample code for working with the three main file system objects i.e. Drives, Directories and Files.

Note

Throughout this series of articles we will be working with file system objects and therefore the code that we will be using to demonstrate the use of the various .Net Framework file system related classes will require the relevant file system security permissions. If you are going to try out any of the sample code, you may need to change some of the various paths and file names used in the code and you will need to ensure that you have the relevant permissions to run the code. The sample code used in the articles does not include any meaningful error handling for reasons of brevity and clarity, the code is for demonstration purposes only. If you intend to use any of the code in your own projects, we strongly recommend that you include robust error handling when working with file system objects.

Path Utility Class

When working with directories, files and to a certain extent drives we will be making extensive use of file system paths, directory names, file names and file extensions. The static Path class contains fields and methods that can be used to validate, construct and deconstruct directory and file paths. Most of the Path class methods do not interact with the file system and the file or path specified in a method call does not have to exist. Changing a file extension, for example, using the ChangeExtension method has no effect on the actual file system object, if it exists.

The Path class fields can be used when constructing paths directly from code to future-proof your code for use on other operating systems. This may seem like overkill but it is as easy to use the Path.DirectorySeparatorChar field as it is to use a literal backslash ("\"), so it makes sense to take advantage of the framework's functionality to isolate your code from platform dependent decisions and assignments. The Path class fields and their, typical, values are as follows:

AltDirectorySeparatorChar
Provides a platform specific alternative character to separate directory levels in a path string. On Windows systems the value is a front-slash ("/").
DirectorySeparatorChar
Provides a platform specific character to separate directory levels in a path string. On Windows systems the value is a backslash ("\").
PathSeparator
Provides a platform specific character to separate path strings in, for example, environment variables or multi-path listings. On Windows systems the value is a semicolon (";").
VolumeSeparatorChar
Provides a platform specific volume separator character. On Windows systems the value is a colon (":").

The Path class methods can be used to construct and deconstruct file system paths, for example, the Combine method can be used to add a file name to a directory path and the GetFileName method can be used to extract a file name from a full path. The path passed to methods can refer to a directory or a file and the path may be absolute or relative, to the current directory, and may also be in Universal Naming Convention (UNC) format. When working with file system objects the paths specified are treated as case insensitive i.e. "C:\Folder\File.ext" and "c:\folder\file.EXT" are assumed to be the same.

The purpose of the Path class methods is evident from the name of each method, however, we strongly recommend that you read the documentation carefully before using a method because a lot of the methods have subtle variations in functionality depending on the value or values passed in. The existing .Net Framework documentation on the Path class is pretty comprehensive, so there seems little point in rehashing it in this article but we will take a look at two of the methods which we think would benefit from further explanation. The GetInvalidFileNameChars method returns an array of the characters that are not allowed in file names and the GetInvalidPathChars method returns an array of characters that are not allowed in path strings. Neither of these two methods performs any processing, such as validation, they just return a Char array containing the relevant invalid characters. This functionality is implemented in methods rather than properties because it is considered bad design to return an array from a property, FxCop will, for example, flag such properties as rule violations.

FxCop

FxCop is a (free) Microsoft tool that analyses .Net assemblies and reports information about the assemblies, such as possible design, localization, performance and security flaws and suggest ways to improve the relevant code. If you are not already using FxCop or a similar tool to analyse your projects as part of the build process we strongly recommend that you download a copy and take a look. The tool is very easy to use, once you get the hang of it and will almost certainly help to improve the quality of your code. FxCop can be downloaded from the following location:

FXCop Download

If you need to validate directory or file names entered by the user you will have to provide your own validation code, similar to the example below.

ExpandSample code to validate a directory path:

The IsInvalidPath method listing above iterates through the array of invalid path characters returned by the Path.GetInvalidPathChars method and checks if the specified directory path includes any of the invalid characters. The method returns true if the specified directory path includes an invalid character or false if the path is valid. The same technique can be used to validate file names using the array of invalid file name characters returned by the Path.GetInvalidFileNameChars method. In addition to validating the characters in a directory path or file name entered by the user you should also check the length of the user supplied value. On Windows systems the length of a fully qualified file name must be less than 260 characters and directory names should be less than 248 characters.

Special Folders

The Windows XP and Windows Vista systems both contain a set of special system folders such as Program Files, Programs, Application Data, Common Application Data, etc. The actual physical location or path of these special folders may be set to default values by the system or may be set by an administrator or the user. Therefore, for example, it is not safe to assume that a machine's Program Files directory can be accessed using the path C:\Program Files, although in most cases the assumed path will be correct we can't be certain that it is.

To access an existing directory or file or to create a new directory or file in one of the system's special folders we first need to retrieve the correct physical path of the relevant special folder. The static Environment class, located in the System namespace contains a method named GetFolderPath that can be used to retrieve the physical path of a specified special folder. The required special folder is specified by passing the relevant Environment.SpecialFolder enumerated value in the call to the GetFolderPath method. Therefore, to retrieve the path of the current user's Application Data folder, we can use the following code:

string dataPath =
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);

On a Windows XP system this call will, typically, return the following path:

C:\Documents and Settings\user name\Application Data

On a Windows Vista system this call will, typically, return the following path:

C:\Users\user name\AppData\Roaming

The fact that XP and Vista machines return different paths for the same special folder illustrates another benefit of special folders, they allow developers to write code that will run as expected on different Windows operating systems, different operating system versions and on machines with different user preference settings.

FileSystemInfo Class

The FileSystemInfo abstract base class provides members that are common to both file and directory file system objects and acts as the base class for both the FileInfo and DirectoryInfo classes. We will be discussing the FileInfo and DirectoryInfo classes in the separate Working With Files In .Net and Working With Directories In .Net articles that are part of the Accessing The File System From .Net series. However, because the functionality provided by the FileSystemInfo class is common to both the FileInfo and DirectoryInfo classes we are going discuss it here.

The FileSystemInfo class member properties mainly manipulate the meta data for both file and directory file system objects, for example, it has properties to access file and directory attributes, creation time, last access time, last write time, etc. The FileSystemInfo class caches its meta data, it does not automatically refresh this data during its lifetime. Therefore, if you have had an instance lying around for a while, you should call the Refresh method to force the instance to reload its file or directory meta data before you access any of the properties.

The FileInfo and DirectoryInfo concrete classes that derive from the FileSystemInfo class are typically used to represent an instance of a file or directory therefore their members are also instance members. The corresponding File and Directory classes, which do not derive from FileSystemInfo, are static and therefore their members require the path or name of a file or directory to work on. It is important to appreciate the differences between these superficially similar pairs of classes. All of the static methods of the File and Directory classes perform security checks, therefore, if you are going to perform several operations you should use the corresponding instance method of the FileInfo or DirectoryInfo class because the security check will not always be required. Conversely, because all File and Directory class methods are static it may be more efficient to use the File or Directory class if you only want to perform a single operation.

Most of the FileSystemInfo class property are self explanatory, for example, the Exists, Extension, FullName and Name properties area read only and return precisely what you would expect. However, the Attributes property and the various *Time and *TimeUtc properties are read/write and are worthy of further discussion.

Attributes Property
The Attribute property is read/write and returns and accepts a FileAttributes enumerated value. The FileAttributes enumeration definition is decorated with the FlagsAttribute attribute making it a bitmapped value that can be used to store a bitwise combination of its member values. Each FileAttributes member represents a single directory or file attribute and the Attributes property may have none, one or many attributes set. Directory and file attributes can be checked, set and cleared using code similar to the example below.
ExpandSample code for setting and clearing directory and file attributes:

The sample code listing above consists of three simple utility methods to test for a specified attribute, clear a specified attribute and set a specified attribute. The Button.Click event code simply toggles the read only attribute of a test file each time that the user clicks the button. The ClearAttribute and SetAttribute methods demonstrate two techniques for reading and writing the Attributes property. The ClearAttribute methods performs the property get and set in a single line of code to clear the specified attribute, whilst the SetAttribute method takes the more deliberate approach of getting the Attributes property value, setting the specified attribute and then setting the new Attributes property value. Both techniques work, however, the ClearAttribute method code is perhaps more suited as an inline statement than a separate method, whilst the SetAttribute method code is clear and self documenting and is perhaps a better solution for a method body.

CreationTime, LastAccesTime and ListWriteTime Properties
The CreationTime, LastAccesTime and ListWriteTime properties are read/write and return and accept a DateTime value. There are also three equivalent coordinated universal time (UTC) versions of these properties that have the value "UTC" appended to the relevant property name. The purpose of these properties is evident from their names however, the LastWriteTime property value is more commonly displayed in user interface components, such as Windows (file) Explorer, as the date modified. It is perhaps more common to read the value of these properties, for display in a user interface component, than it is to write to them. However, when copying or moving directories and files the creation time values get set to the date and time of the copy or move and this is not always what is required. The CreationTime property can be used to reset the value of a copied directory or file using code similar to the example below.
ExpandSample code for resetting the creation time of a copied file:

The sample code listing above creates a FileSystemInfo derived FileInfo class to represent the file we are going to copy and retrieves and caches its CreationTime property value. The source file is then copied to a different, destination, location and a FileInfo instance is created to represent the destination file. The CreationTime property of the destination file is then updated to reflect the creation time of the original source file. We have used the FileSystemInfo derived FileInfo class throughout the example, however, in this type of scenario it would be more efficient to use the static File.SetCreationTime method to update the creation time of the destination file to avoid the overhead of creating the destination FileInfo instance. We could have used the static File class to perform the complete operation but that would have involved a security request for each stage of the operation and may be less efficient for caching the creation time and copying the file. When working with a single directory or file the choice between the FileSystemInfo derived classes and their static equivalents can be difficult to make, however, when working with many directories and files or when performing many operations on the same directory or file, the FileSystemInfo derived classes generally perform better.

Access Control and Audit Security

When creating or working with directories and files we can, since version 2.0 of the framework, create and modify the access control list (ACL) that is associated with a file system object. The System.Security.AccessControl namespace contains types that can be used to define access and audit security rules for directories and files and the relevant types in the System.IO namespace have new members that can make use of these types to get and set directory and file ACLs. Access and audit security rules do not have to be set when creating directories and files from code and in a lot of situations it will not be possible to do so because the code will not have the required domain or machine account details. If access and/or audit rules are not specified at creation time the directory or file with be assigned the inherited and/or appropriate default values by the system. Because assigning access control and audit rules are "optional extras" and including them may obscure the main purpose of the sample code we will not be including this functionality in the code samples used in this series of articles. However, we will include a brief overview of the relevant classes and show how they may be used in this introduction because security permissions are a vital constituent of the file system.

Each directory and file has a discretionary access control list (DACL) that controls access to the directory or file and a system access control list (SACL) that specifies which access control attempts are audited. The FileSystemAccessRule class represents the DACLs and the FileSystemAuditRule class represents the SACLs and together these two classes make up the access control entries (ACEs) that are associated with a directory or file. Instances of the FileSystemAccessRule class and the FileSystemAuditRule class can be added to the DirectorySecurity class, for a directory or the FileSecurity class, for a file, to specify access and audit security permissions. You should note that for auditing to work you must enable the Audit Access Security policy on the machine and an audit rule for an account requires a corresponding access rule for the same account. If we put all of this together we can add access control to a directory during the create operation using code similar to the example below.

ExpandCreating a directory with access control:

The sample code listing above creates a DirectorySecurity instance from an existing parent directory access control section and then adds an allow write data access permission for "TestUser", we are assuming that "TestUser" will inherit an allow read data permission from the parent directory. The static Directory.CreateDirectory method is then used to create a sub-directory with the required access security rules. The DirectorySecurity class has a default, parameterless constructor that creates an empty DirectorySecurity instance that can be populated with a complete set of access and audit security rules. The FileSystemRights enumeration is decorated with the FlagsAttribute attribute, therefore multiple values can be specified and the AcessControlType enumeration defines Allow and Deny members. The sample code ignores the return value of the CreateDirectory method as it is not required.

The process for specifying access and audit security rules when creating a file is similar to the directory sample shown above although, obviously, we would need to use the relevant file related classes. Both the static Directory and the static File classes have GetAccessControl and SetAccessControl methods as do the DirectoryInfo and FileInfo classes, so it is fairly straight forward to write code to modify the access and audit security rules of an existing directory or file.

Working With File System Objects

There are four articles in the Accessing The File System From .Net series, this article introduces the relevant utility and base classes from the System.IO namespace and then there are a further three articles that discuss working with each of the main file system objects, Drives, Directories and Files in detail. You can use the links provided below to navigate to the particular file system topic that you are interested in.

If you are interested in information on copying, moving and deleting directories and files using the classes provided in the .Net Framework then read on, however, if you are interested in navigating the file system and/or displaying file system information in a user interface component then you should take a look at the File System UI component and class library provided by Blayd Software. The library contains data source components that can be used to list drives, folders and files and user interface controls that can be data bound to the data source components to display drives, folders and files. In addition to the File System UI library Blayd Software also provide a free file open dialog component that includes an image preview pain. The dialog allows the user to view a preview of various types of image file before selecting a file to open. The dialog also demonstrates the components and controls contained in the File System UI library.

Working With Drives In .Net

Discusses the DriveInfo class in detail and provides sample code that demonstrates how to make use of the class in your own projects to display information about a machine's drives to the user.

Working With Directories In .Net

Discusses the Directory and DirectoryInfo classes in detail and provides sample code that demonstrates how to use both of these classes to create, copy, move, rename and delete directories in your own projects.

Working With Files In .Net

Discusses the File and FileInfo classes in detail and provides sample code that demonstrates how to use both of these classes to create, copy, move, rename and delete files in your own projects.