¿Cómo puede comprobar fácilmente si se deniega el acceso a un archivo en .NET?

100

Básicamente, me gustaría comprobar si tengo derechos para abrir el archivo antes de intentar abrirlo; No quiero usar un try / catch para esta comprobación a menos que sea necesario. ¿Existe alguna propiedad de acceso a archivos que pueda comprobar de antemano?

Horas
fuente
2
Título cuando cambié la etiqueta: "estoy corrigiendo". No es broma.
Joel Coehoorn
6
De acuerdo, desearía que hubiera un TryOpen (es decir, un patrón Try-Parse).
Tristan

Respuestas:

157

He hecho esto innumerables veces en el pasado, y casi todas las veces que lo he hecho, me equivoqué al intentarlo.

Los permisos de los archivos (incluso la existencia de archivos) son volátiles : pueden cambiar en cualquier momento. Gracias a la Ley de Murphy, esto incluye especialmente el breve período entre el momento en que revisa el archivo y el momento en que intenta abrirlo. Es incluso más probable que se produzca un cambio si se encuentra en un área donde sabe que debe verificar primero. Sin embargo, por extraño que parezca, nunca sucederá en sus entornos de prueba o desarrollo, que tienden a ser bastante estáticos. Esto hace que el problema sea difícil de localizar más adelante y facilita que este tipo de error llegue a producción.

Lo que esto significa es que aún debe poder manejar la excepción si los permisos o la existencia del archivo son malos, a pesar de su verificación. Se requiere un código de manejo de excepciones , ya sea que verifique o no los permisos del archivo con anticipación. El código de manejo de excepciones proporciona toda la funcionalidad de las comprobaciones de existencia o permisos. Además, aunque se sabe que los controladores de excepciones como este son lentos, es importante recordar que la E / S del disco es aún más lenta ... mucho más lenta ... y llamar a la función .Exists () o verificar los permisos forzará un viaje adicional fuera del sistema de archivos.

En resumen, una verificación inicial antes de intentar abrir el archivo es redundante y un desperdicio. No hay ningún beneficio adicional sobre el manejo de excepciones, en realidad perjudicará, no ayudará, su rendimiento, agrega costos en términos de más código que debe mantenerse y puede introducir errores sutiles en su código. Simplemente no hay ninguna ventaja en hacer la verificación inicial. En cambio, lo correcto aquí es simplemente intentar abrir el archivo y esforzarse en un buen controlador de excepciones si falla. Lo mismo es cierto incluso si solo está comprobando si el archivo existe o no. Este razonamiento se aplica a cualquier recurso volátil.

Joel Coehoorn
fuente
5
Exactamente. Este es un ejemplo clásico de condición de carrera.
Powerlord
3
korro: de todos modos, debe poder manejar los permisos incorrectos en caso de falla, y eso hace que la verificación inicial sea redundante y derrochadora.
Joel Coehoorn
2
Una verificación inicial puede ayudar a manejar correctamente errores específicos comunes; mirar hacia el futuro es a menudo más fácil que hacer coincidir atributos de excepción particulares con causas específicas. El try / catch sigue siendo obligatorio.
peterchen
5
Esta respuesta no responde a la pregunta "cómo comprobar si tengo derechos para abrir el archivo" en algún caso antes de intentar abrirlo. Es muy posible que el caso sea que si no se permite el permiso en ese caso, el software no intentará leer el archivo, incluso si el permiso se puede otorgar justo después de que se hayan verificado los permisos.
Triynko
5
No importa si los permisos son volátiles, cuando solo te importa lo que son en ese instante. La falla siempre debe manejarse, pero si busca un permiso de lectura y no está allí, es posible que desee omitir la lectura del archivo, incluso si es posible que un segundo después tenga acceso. Tienes que poner un límite.
Triynko
25

Consejo rápido para cualquiera que venga aquí con un problema similar:

Tenga cuidado con las aplicaciones de sincronización web como DropBox. Acabo de pasar 2 horas pensando que la instrucción "using" (patrón Dispose) está rota en .NET.

Finalmente me di cuenta de que Dropbox lee y escribe archivos continuamente en segundo plano para sincronizarlos.

¿Adivina dónde se encuentra mi carpeta de proyectos de Visual Studio? Dentro de la carpeta "Mi Dropbox", por supuesto.

Por lo tanto, mientras ejecutaba mi aplicación en modo de depuración, DropBox también accedía continuamente a los archivos que estaba leyendo y escribiendo para sincronizarlos con el servidor de DropBox. Esto provocó los conflictos de bloqueo / acceso.

Entonces, al menos ahora sé que necesito una función de apertura de archivo más robusta (es decir, TryOpen () que hará varios intentos). Me sorprende que aún no sea una parte incorporada del marco.

[Actualizar]

Aquí está mi función de ayuda:

/// <summary>
/// Tries to open a file, with a user defined number of attempt and Sleep delay between attempts.
/// </summary>
/// <param name="filePath">The full file path to be opened</param>
/// <param name="fileMode">Required file mode enum value(see MSDN documentation)</param>
/// <param name="fileAccess">Required file access enum value(see MSDN documentation)</param>
/// <param name="fileShare">Required file share enum value(see MSDN documentation)</param>
/// <param name="maximumAttempts">The total number of attempts to make (multiply by attemptWaitMS for the maximum time the function with Try opening the file)</param>
/// <param name="attemptWaitMS">The delay in Milliseconds between each attempt.</param>
/// <returns>A valid FileStream object for the opened file, or null if the File could not be opened after the required attempts</returns>
public FileStream TryOpen(string filePath, FileMode fileMode, FileAccess fileAccess,FileShare fileShare,int maximumAttempts,int attemptWaitMS)
{
    FileStream fs = null;
    int attempts = 0;

    // Loop allow multiple attempts
    while (true)
    {
        try
        {
            fs = File.Open(filePath, fileMode, fileAccess, fileShare);

            //If we get here, the File.Open succeeded, so break out of the loop and return the FileStream
            break;
        }
        catch (IOException ioEx)
        {
            // IOExcception is thrown if the file is in use by another process.

            // Check the numbere of attempts to ensure no infinite loop
            attempts++;
            if (attempts > maximumAttempts)
            {
                // Too many attempts,cannot Open File, break and return null 
                fs = null;
                break;
            }
            else
            {
                // Sleep before making another attempt
                Thread.Sleep(attemptWaitMS);

            }

        }

    }
    // Reutn the filestream, may be valid or null
    return fs;
}
Ceniza
fuente
3
@ Ash Creo que no leíste la pregunta correctamente. Él quiere evitar intentar atrapar.
Ravisha
10
@Ravisha, ¿leíste la respuesta más votada de Joel? Como dice Joel, "Lo que haces es intentar abrir el archivo y manejar la excepción si falla" . Por favor, no vote negativamente solo porque no le gusta el hecho de que algo no se puede evitar.
Ash
¡Gracias por el código! Una cosa, podría ser mejor usar usando, por ejemplo, vea la respuesta de Tazeem aquí
Cel
Sin embargo, dado que devuelve el flujo de archivos, la usingpersona que llama tendría que usarlo ...
Cel
@Cel: usingno funcionará aquí. Al final del bloque de uso, fsse forzará el cierre. ¡Le dará a la persona que llama un flujo de archivos CERRADO (tan inútil)!
ToolmakerSteve
4

Aquí está la solución que busca

var fileIOPermission = new FileIOPermission(FileIOPermissionAccess.Read,
                                            System.Security.AccessControl.AccessControlActions.View,
                                            MyPath);

if (fileIOPermission.AllFiles == FileIOPermissionAccess.Read)
{
    // Do your thing here...
}

esto crea un nuevo permiso de lectura basado en la vista de la ruta de todos los archivos y luego verifica si es igual al acceso a los archivos leído.

dj shahar
fuente
3

Primero, lo que dijo Joel Coehoorn.

Además: debe examinar las suposiciones que subyacen a su deseo de evitar el uso de try / catch a menos que sea necesario. La razón típica para evitar la lógica que depende de excepciones (la creación de Exceptionobjetos funciona mal) probablemente no sea relevante para el código que abre un archivo.

Supongo que si está escribiendo un método que rellena un List<FileStream>archivo abriendo cada archivo en un subárbol de directorio y esperaba que una gran cantidad de ellos fueran inaccesibles, es posible que desee verificar los permisos de archivo antes de intentar abrir un archivo para que no lo haya hecho. obtener demasiadas excepciones. Pero aún manejarías la excepción. Además, probablemente haya algo terriblemente mal con el diseño de su programa si está escribiendo un método que hace esto.

Robert Rossney
fuente
-1
public static bool IsFileLocked(string filename)
        {
            bool Locked = false;
            try
            {
                FileStream fs =
                    File.Open(filename, FileMode.OpenOrCreate,
                    FileAccess.ReadWrite, FileShare.None);
                fs.Close();
            }
            catch (IOException ex)
            {
                Locked = true;
            }
            return Locked;
        }
Omid Matouri
fuente
-3
public static FileStream GetFileStream(String filePath, FileMode fileMode, FileAccess fileAccess, FileShare fileShare, ref int attempts, int attemptWaitInMilliseconds)
{            
    try
    {
         return File.Open(filePath, fileMode, fileAccess, fileShare);
    }
    catch (UnauthorizedAccessException unauthorizedAccessException)
    {
        if (attempts <= 0)
        {
            throw unauthorizedAccessException;
        }
        else
        {
            Thread.Sleep(attemptWaitInMilliseconds);
            attempts--;
            return GetFileStream(filePath, fileMode, fileAccess, fileShare, ref attempts, attemptWaitInMilliseconds);
        }
    }
}
Rudzitis
fuente
8
-1: usa "lanzar"; no "lanzar unauthorizedAccessException;". Estás perdiendo el rastro de tu pila.
John Saunders
¿Por qué se attemptspasa por ref? Eso no tiene sentido. Tampoco prueba para en <=lugar de solo ==.
Konrad Rudolph
1
@John: bueno, en este caso es deseable perder el rastro de la pila (profundamente anidado) de la llamada recursiva, así que creo que en este caso throw exes lo correcto .
Konrad Rudolph
2
@Konrad: @Rudzitis: Estoy cambiando mi razón para el -1. Es peor que arruinar la pila con "lanzar ex". Estás arruinando la pila al inducir artificialmente niveles de pila adicionales a través de la recursividad en un momento en el que la profundidad de la pila realmente importa. Este es un problema iterativo, no recursivo.
John Saunders