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.
Sample code to validate a directory path:
private bool IsInvalidPath(string directory)
{
bool invalid = string.IsNullOrEmpty(directory);
if (!invalid)
{
char[] invalidPathChars = Path.GetInvalidPathChars();
foreach (char value in invalidPathChars)
{
if (directory.IndexOf(value) > -1)
{
invalid = true;
break;
}
}
}
return invalid;
}
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.
Sample code for setting and clearing directory and file
attributes:
private bool IsAttributeSet(FileSystemInfo info, FileAttributes attribute)
{
return ((info.Attributes & attribute) == attribute);
}
private void ClearAttribute(FileSystemInfo info, FileAttributes attribute)
{
info.Attributes &= ~attribute;
}
private void SetAttribute(FileSystemInfo info, FileAttributes attribute)
{
FileAttributes attributes = info.Attributes;
attributes |= attribute;
info.Attributes = attributes;
}
private void buttonFileSystemInfo_Click(object sender, EventArgs e)
{
FileInfo info = new FileInfo("C:\\Testing\\ATestFile.txt");
if (IsAttributeSet(info, FileAttributes.ReadOnly))
{
ClearAttribute(info, FileAttributes.ReadOnly);
}
else
{
SetAttribute(info, FileAttributes.ReadOnly);
}
}
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.
Sample code for resetting the creation time of a copied
file:
FileInfo info = new FileInfo("C:\\Testing\\ATestFile.txt");
DateTime creation = info.CreationTime;
info.CopyTo("C:\\Testing\\TestDestination\\ATestFile.txt");
FileInfo destInfo = new FileInfo("C:\\Testing\\TestDestination\\ATestFile.txt");
destInfo.CreationTime = creation;
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.
Creating a directory with access control:
DirectorySecurity security = new DirectorySecurity("C:\\Testing",
AccessControlSections.Access);
security.AddAccessRule(new FileSystemAccessRule("XPDEV005\\TestUser",
FileSystemRights.WriteData,
AccessControlType.Allow));
Directory.CreateDirectory("C:\\Testing\\TestSubdir", security);
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.
File System UI Features: www.blayd.co.uk/product.aspx?pageid=955610043
Open Image File Dialog Features: www.blayd.co.uk/product.aspx?pageid=838416243
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.
Read article at: www.blayd.co.uk/article.aspx?pageid=1029
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.
Read article at: www.blayd.co.uk/article.aspx?pageid=1030
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.
Read article at: www.blayd.co.uk/article.aspx?pageid=1031
Copyright ©Blayd Software Limited 2008-2010. All rights reserved.