Xceed .NET Libraries Documentation
PathException event
Welcome to Xceed .NET, .NET Standard and Xamarin Libraries! > Basic Concepts > Events > PathException event

The PathException event is raised when an exception is caught during the processing of a path System.String, allowing the handler to correct the path and retry the operation.

Purpose

The PathException event provides a convenient way to react when a path is deemed invalid for the FileSystem type.

While the FileSystem API provides common properties and methods for different mediums, the path limits for a DiskFile/DiskFolder (260 characters) is different than for a ZippedFile/ZippedFolder (>60000 characters).

Because of this, problems can occur when copying files from one medium to another. If, for example, you unzip a file that has a name longer than 260 characters to a DiskFolder, the DiskFolder class will not be able to create a DiskFile in that folder that represents the target file because the source name is invalid for that medium. By default, an exception will be thrown and the operation will fail.

Before an exception is thrown, the PathException event is raised. It provides event handlers with a modifiable path System.String that caused the failure, the type of FileSystem class involved in the attempt and an action code that tells the component how to proceed (abort, retry, ignore).

An event handler can therefore handle path situations, manipulate the offending to something acceptable for the target FileSystem type and continue with the operation.

Basic steps

To subscribe to the PathException event, the following steps must be performed:

Demonstration

This example demonstrates how to subscribe to the PathException event, a situation where the event will be raised and a possible implementation of an event handler.

static void PathExceptionExample()
{
  // Get a reference to a source file
  AbstractFile sourceFile = new DiskFile( "SomeFile.dat" );

  // Get a reference to a zip archive
  AbstractFile zipFile = new DiskFile( "PathExceptionExample.zip" );
  Xceed.Zip.ZipArchive zip = new Xceed.Zip.ZipArchive( zipFile );

  // Compute a very long file name (512 characters)
  StringBuilder stringBuilder = new StringBuilder( );
  for( int i = 0; i < 32; i++ )
    stringBuilder.Append( "VeryLongFilename" );
  stringBuilder.Append( ".dat" );

  using( AutoBatchUpdate batch = new AutoBatchUpdate( zip ) )
  {
    // Get a reference to a zipped file that will have the very long file name
    AbstractFile zippedFile = zip.GetFile( stringBuilder.ToString() );

    // Zip the source file to that zipped file
    sourceFile.CopyTo( zippedFile, true );

    // Get a reference to a zipped file that has invalid characters
    zippedFile = zip.GetFile( "P\"ath*Exception?Example.dat" );

    // Zip the source file to that zipped file
    sourceFile.CopyTo( zippedFile, true );
  }

  /* Create a reference to a FileSystemEvents object */
  FileSystemEvents events = new FileSystemEvents();

  /* Subscribe to the PathException event of the FileSystemEvents object using the PathExceptionEventHandler delegate */
  events.PathException += new PathExceptionEventHandler( OnPathException );

  // Get a reference to a folder on a hard disk
  AbstractFolder destinationFolder = new DiskFolder( "SomeFolder" );

  /* Here we extract the files from the archive. But the file in the archive
   * with the long filename can't be expressed on the Windows file system as it is
   * implemented by the .NET framework. The PathException event handler will allow
   * us to intervene before the operation throws an exception. */

  // Extract the files from the archive
  zip.CopyFilesTo( events, null, destinationFolder, true, true );
}

/// <summary>
/// Finds an exception amongst the supplied exception stack
/// </summary>
/// <typeparam name="T">The exception type to find</typeparam>
/// <param name="exception">The exception stack</param>
/// <returns>An exception object of the specified Type or null</returns>
static T FindException<T>( Exception exception ) where T : Exception
{
  T target = default( T );

  // While we have an exception
  while( exception != null )
  {
    // If we can express the current exception as the target type
    if( ( target = exception as T ) != null )
    {
      // We're done
      break;
    }

    // Go to the next exception
    exception = exception.InnerException;
  }

  // Return what we found
  return target;
}

/// <summary>
/// Replaces a single character in the supplied String object at the specified index
/// </summary>
/// <param name="s"></param>
/// <param name="index"></param>
/// <param name="newChar"></param>
/// <returns>A String object with the replaced character</returns>
static string ReplaceChar( string s, int index, char newChar )
{
  // Put the path in a string builder
  System.Text.StringBuilder builder = new System.Text.StringBuilder( s );

  // Replace that invalid character with 'newChar'
  builder[ index ] = newChar;

  // Update the path with the new value without the offending character
  s = builder.ToString();

  return s;
}

static string ComputeSmallerName( string path, PathExceptionEventArgs e )
{
  // TODO: Implement logic to compute a smaller name depending on your scenario and needs
  return null;
}

static void OnPathException( object sender, PathExceptionEventArgs e )
{
  // Try to find a InvalidCharacterInPathException in the exception stack we got from the event
  InvalidCharacterInPathException invalidCharacterInPathException = FindException<InvalidCharacterInPathException>( e.Exception );
  PathTooLongException pathTooLongException = FindException<PathTooLongException>( e.Exception );
  ItemIsFolderException itemIsFolderException = FindException<ItemIsFolderException>( e.Exception );
  ItemIsFileException itemIsFileException = FindException<ItemIsFileException>( e.Exception );
  ArgumentException argumentException = FindException<ArgumentException>( e.Exception );

  // If part of the path failure was caused by an invalid character
  if( invalidCharacterInPathException != null )
  {
    /* The InvalidCharacterInPathException gives us exactly what character is invalid */

    e.Path = ReplaceChar( e.Path, invalidCharacterInPathException.InvalidCharIndex, '_' );

    // Have the FileSystem retry using the path
    e.Action = ItemExceptionAction.Retry;
  }
  else if( pathTooLongException != null )
  {
    // If the exception occurred for a folder
    if( e.FileSystemType == typeof( AbstractFolder ) && e.Path.Length < 248 )
    {
      /* It's the combined length of all the preceding folder names that makes
       * this folder name bust the limit */

      // Try to create 'space' in the path by not using the folder name at all
      // and use the parent folder instead
      e.Path = "..";

      // Tell the component to retry using the new value we will put in e.Path
      e.Action = ItemExceptionAction.Retry;
    }
    else
    {
      /* TODO: Implement ComputeSmallerName() to compute a small folder name and assign it for trial */
      string smallerName = ComputeSmallerName( e.Path, e );

      // If a smaller name could be computed
      if( String.IsNullOrEmpty( smallerName ) )
      {
        // Skip the item
        e.Action = ItemExceptionAction.Ignore;
      }
      else
      {
        e.Path = smallerName;

        // Tell the component to retry using the new value we will put in e.Path
        e.Action = ItemExceptionAction.Retry;
      }
    }
  }
  else if( itemIsFolderException != null )
  {
    /* Here, we are creating an AbstractFile object but the path represents an existing
     folder.
     
     There's not much we can do to fix that. If it makes sense in your scenario, you
     can always try to ignore the item and move on to the next one. Note that 'ignore'
     only applies when processing a list of items. */
    //e.Action = ItemExceptionAction.Ignore;
  }
  else if( itemIsFileException != null )
  {
    /* Here, we are creating an AbstractFolder object but the path represents an existing
    file.
     
    There's not much we can do to fix that. If it makes sense in your scenario, you
    can always try to ignore the item and move on to the next one. Note that 'ignore'
    only applies when processing a list of items. */
    //e.Action = ItemExceptionAction.Ignore;
  }
  /* NOTE: Order is important here. ArgumentException is the more general exception that usually
  contains more specific inner exceptions. We test for these specific exceptions above and
  handle the more general exceptions last. */
  else if( argumentException != null )
  {
    /* ArgumentException is more complicated as it is less defined. It is an umbrella exception 
    that is usually thrown when a .NET framework method or Windows itself can't process the path
    for 'some' reason.
     
    One thing that is worth checking is more invalid characters. System.IO.Path.GetInvalidPathChars()
    is what is used by the component to check the path with. But it doesn't contain things like
    '?', '*'. Most likely because in some contexts, they are valid. But not when you want to
    manipulate an actual file or folder. */

    // If the problem argument is the filename
    if( argumentException.ParamName == "fileName" )
    {
      // Common 'invalid' characters that are not caught by System.IO.Path.GetInvalidPathChars()
      char[] furtherInvalidChars = new char[] { '?', '*' };

      int index;
      if( ( index = e.Path.IndexOfAny( furtherInvalidChars ) ) != -1 )
      {
        e.Path = ReplaceChar( e.Path, index, '_' );

        // Have the FileSystem retry using the path
        e.Action = ItemExceptionAction.Retry;
      }
    }
  }
}
    Private Shared Sub PathExceptionExample()
      ' Get a reference to a source file
      Dim sourceFile As AbstractFile = New DiskFile("SomeFile.dat")

      ' Get a reference to a zip archive
      Dim zipFile As AbstractFile = New DiskFile("PathExceptionExample.zip")
      Dim zip As New Xceed.Zip.ZipArchive(zipFile)

      ' Compute a very long file name (512 characters)
      Dim stringBuilder As New StringBuilder()
      For i As Integer = 0 To 31
        stringBuilder.Append("VeryLongFilename")
      Next i
      stringBuilder.Append(".dat")

      Using batch As New AutoBatchUpdate(zip)
        ' Get a reference to a zipped file that will have the very long file name
        Dim zippedFile As AbstractFile = zip.GetFile(stringBuilder.ToString())

        ' Zip the source file to that zipped file
        sourceFile.CopyTo(zippedFile, True)

        ' Get a reference to a zipped file that has invalid characters
        zippedFile = zip.GetFile("P""ath*Exception?Example.dat")

        ' Zip the source file to that zipped file
        sourceFile.CopyTo(zippedFile, True)
      End Using

      ' Create a reference to a FileSystemEvents object 
      Dim events As New FileSystemEvents()

      ' Subscribe to the PathException event of the FileSystemEvents object using the PathExceptionEventHandler delegate 
      AddHandler events.PathException, AddressOf OnPathException

      ' Get a reference to a folder on a hard disk
      Dim destinationFolder As AbstractFolder = New DiskFolder("SomeFolder")

'       Here we extract the files from the archive. But the file in the archive
'       * with the long filename can't be expressed on the Windows file system as it is
'       * implemented by the .NET framework. The PathException event handler will allow
'       * us to intervene before the operation throws an exception. 

      ' Extract the files from the archive
      zip.CopyFilesTo(events, Nothing, destinationFolder, True, True)
    End Sub

    ''' <summary>
    ''' Finds an exception amongst the supplied exception stack
    ''' </summary>
    ''' <typeparam name="T">The exception type to find</typeparam>
    ''' <param name="exception">The exception stack</param>
    ''' <returns>An exception object of the specified Type or null</returns>
    Private Shared Function FindException(Of T As Exception)(ByVal exception As Exception) As T
      Dim target As T = Nothing

      ' While we have an exception
      Do While exception IsNot Nothing
        ' If we can express the current exception as the target type
        target = TryCast(exception, T)
        If target IsNot Nothing Then
          ' We're done
          Exit Do
        End If

        ' Go to the next exception
        exception = exception.InnerException
      Loop

      ' Return what we found
      Return target
    End Function

    ''' <summary>
    ''' Replaces a single character in the supplied String object at the specified index
    ''' </summary>
    ''' <param name="s"></param>
    ''' <param name="index"></param>
    ''' <param name="newChar"></param>
    ''' <returns>A String object with the replaced character</returns>
    Private Shared Function ReplaceChar(ByVal s As String, ByVal index As Integer, ByVal newChar As Char) As String
      ' Put the path in a string builder
      Dim builder As New System.Text.StringBuilder(s)

      ' Replace that invalid character with 'newChar'
      builder(index) = newChar

      ' Update the path with the new value without the offending character
      s = builder.ToString()

      Return s
    End Function

    Private Shared Function ComputeSmallerName(ByVal path As String, ByVal e As PathExceptionEventArgs) As String
      ' TODO: Implement logic to compute a smaller name depending on your scenario and needs
      Return Nothing
    End Function

    Private Shared Sub OnPathException(ByVal sender As Object, ByVal e As PathExceptionEventArgs)
      ' Try to find a InvalidCharacterInPathException in the exception stack we got from the event
      Dim invalidCharacterInPathException As InvalidCharacterInPathException = FindException(Of InvalidCharacterInPathException)(e.Exception)
      Dim pathTooLongException As PathTooLongException = FindException(Of PathTooLongException)(e.Exception)
      Dim itemIsFolderException As ItemIsFolderException = FindException(Of ItemIsFolderException)(e.Exception)
      Dim itemIsFileException As ItemIsFileException = FindException(Of ItemIsFileException)(e.Exception)
      Dim argumentException As ArgumentException = FindException(Of ArgumentException)(e.Exception)

      ' If part of the path failure was caused by an invalid character
      If invalidCharacterInPathException IsNot Nothing Then
        ' The InvalidCharacterInPathException gives us exactly what character is invalid 

        e.Path = ReplaceChar(e.Path, invalidCharacterInPathException.InvalidCharIndex, "_"c)

        ' Have the FileSystem retry using the path
        e.Action = ItemExceptionAction.Retry
      ElseIf pathTooLongException IsNot Nothing Then
        ' If the exception occurred for a folder
        If e.FileSystemType Is GetType(AbstractFolder) AndAlso e.Path.Length < 248 Then
'           It's the combined length of all the preceding folder names that makes
'           * this folder name bust the limit 

          ' Try to create 'space' in the path by not using the folder name at all
          ' and use the parent folder instead
          e.Path = ".."

          ' Tell the component to retry using the new value we will put in e.Path
          e.Action = ItemExceptionAction.Retry
        Else
          ' TODO: Implement ComputeSmallerName() to compute a small folder name and assign it for trial 
          Dim smallerName As String = ComputeSmallerName(e.Path, e)

          ' If a smaller name could be computed
          If String.IsNullOrEmpty(smallerName) Then
            ' Skip the item
            e.Action = ItemExceptionAction.Ignore
          Else
            e.Path = smallerName

            ' Tell the component to retry using the new value we will put in e.Path
            e.Action = ItemExceptionAction.Retry
          End If
        End If
      ElseIf itemIsFolderException IsNot Nothing Then
'         Here, we are creating an AbstractFile object but the path represents an existing
'         folder.
'         
'         There's not much we can do to fix that. If it makes sense in your scenario, you
'         can always try to ignore the item and move on to the next one. Note that 'ignore'
'         only applies when processing a list of items. 
        'e.Action = ItemExceptionAction.Ignore;
      ElseIf itemIsFileException IsNot Nothing Then
'         Here, we are creating an AbstractFolder object but the path represents an existing
'        file.
'         
'        There's not much we can do to fix that. If it makes sense in your scenario, you
'        can always try to ignore the item and move on to the next one. Note that 'ignore'
'        only applies when processing a list of items. 
        'e.Action = ItemExceptionAction.Ignore;
'       NOTE: Order is important here. ArgumentException is the more general exception that usually
'      contains more specific inner exceptions. We test for these specific exceptions above and
'      handle the more general exceptions last. 
      ElseIf argumentException IsNot Nothing Then
'         ArgumentException is more complicated as it is less defined. It is an umbrella exception
'        that is usually thrown when a .NET framework method or Windows itself can't process the path
'        for 'some' reason.
'         
'        One thing that is worth checking is more invalid characters. System.IO.Path.GetInvalidPathChars()
'        is what is used by the component to check the path with. But it doesn't contain things like
'        '?', '*'. Most likely because in some contexts, they are valid. But not when you want to
'        manipulate an actual file or folder. 

        ' If the problem argument is the filename
        If argumentException.ParamName = "fileName" Then
          ' Common 'invalid' characters that are not caught by System.IO.Path.GetInvalidPathChars()
          Dim furtherInvalidChars() As Char = { "?"c, "*"c }

          Dim index As Integer
          index = e.Path.IndexOfAny(furtherInvalidChars)
          If index <> -1 Then
            e.Path = ReplaceChar(e.Path, index, "_"c)

            ' Have the FileSystem retry using the path
            e.Action = ItemExceptionAction.Retry
          End If
        End If
      End If
    End Sub