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