web-dev-qa-db-fra.com

Comment puis-je convertir Assembly.CodeBase en un chemin de système de fichiers en C #?

J'ai un projet qui stocke des modèles dans un \Templates dossier à côté des DLL et EXE.

Je veux déterminer ce chemin de fichier au moment de l'exécution, mais en utilisant une technique qui fonctionnera à l'intérieur d'un test unitaire ainsi qu'en production (et je ne veux pas désactiver le cliché instantané dans NUnit!)

Assembly.Location n'est pas bon car il renvoie le chemin de l'assembly copié en ombre lors de l'exécution sous NUnit.

Environment.CommandLine est également d'une utilité limitée car dans NUnit et al, il renvoie le chemin vers NUnit, pas vers mon projet.

Assembly.CodeBase semble prometteur, mais c'est un chemin UNC:

file:///D:/projects/MyApp/MyApp/bin/debug/MyApp.exe

Maintenant, je pourrais transformer cela en un chemin de système de fichiers local en utilisant la manipulation de chaînes, mais je soupçonne qu'il existe une façon plus propre de le faire enfoui quelque part dans le framework .NET. Quelqu'un connaît une façon recommandée de faire cela?

(Lancement d'une exception si le chemin UNC n'est pas un file:/// L'URL est très bien dans ce contexte)

63
Dylan Beattie

Vous devez utiliser System.Uri.LocalPath:

string localPath = new Uri("file:///D:/projects/MyApp/MyApp/bin/debug/MyApp.exe").LocalPath;

Donc, si vous voulez l'emplacement d'origine de l'assembly en cours d'exécution:

string localPath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath;
105
Polyfun

Assembly.CodeBase semble prometteur, mais c'est un chemin UNC:

Notez que c'est quelque chose qui se rapproche d'un fichier uri , pas an chemin UNC .


Vous résolvez cela en faisant la manipulation de chaînes à la main. Sérieusement.

Essayez toutes les autres méthodes que vous pouvez trouver sur SO avec le répertoire suivant (textuellement):

C:\Test\Space( )(h#)(p%20){[a&],t@,p%,+}.,\Release

Il s'agit d'un chemin Windows valide, quoique quelque peu inhabituel. (Certaines personnes le feront ont l'un ou l'autre de ces caractères dans ces chemins, et vous voudriez que votre méthode fonctionne pour tous de ceux-ci, non?)

Les propriétés de base de code disponibles ( nous ne voulons pas de Location, non? ) sont alors (sur mon Win7 avec .NET 4):

Assembly.CodeBase -> file:///C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release

Assembly.EscapedCodeBase -> file:///C:/Test/Space(%20)(h%23)(p%20)%7B%5Ba%26%5D,t@,p%,+%7D.,/Release

Vous noterez:

  • CodeBase n'est pas échappé du tout, c'est juste le chemin local régulier préfixé avec file:/// et les barres obliques inversées ont été remplacées. En tant que tel, cela ne fonctionne pas fonctionne pour alimenter ceci à System.Uri.
  • EscapedCodeBase n'est pas complètement échappé (je fais pas sais s'il s'agit d'un bug ou s'il s'agit d'une lacune du schéma URI ):
    • Notez comment le caractère espace () se traduit par %20
    • mais le %20 séquence aussi se traduit par %20! (pourcentage % n'est pas échappé du tout)
    • Personne peut reconstruire l'original à partir de cette forme mutilée!

Pour les fichiers locaux (Et c'est vraiment tout ce qui m'importe pour le CodeBasestuff, car si le fichier n'est pas local, vous voudrez probablement utiliser .Location de toute façon, ce qui suit fonctionne pour moi (notez que ce n'est pas le plus joli non plus:

    public static string GetAssemblyFullPath(Assembly assembly)
    {
        string codeBasePseudoUrl = Assembly.CodeBase; // "pseudo" because it is not properly escaped
        if (codeBasePseudoUrl != null) {
            const string filePrefix3 = @"file:///";
            if (codeBasePseudoUrl.StartsWith(filePrefix3)) {
                string sPath = codeBasePseudoUrl.Substring(filePrefix3.Length);
                string bsPath = sPath.Replace('/', '\\');
                Console.WriteLine("bsPath: " + bsPath);
                string fp = Path.GetFullPath(bsPath);
                Console.WriteLine("fp: " + fp);
                return fp;
            }
        }
        System.Diagnostics.Debug.Assert(false, "CodeBase evaluation failed! - Using Location as fallback.");
        return Path.GetFullPath(Assembly.Location);
    }

Je suis sûr que l'on peut trouver de meilleures solutions, probablement on pourrait même trouver une solution qui fait un bon encodage/décodage d'URL de la propriété CodeBase si c'est un chemin local, mais étant donné qu'on peut simplement supprimer en dehors de file:/// et en finir, je dirais que cette solution est assez bonne, si certainement vraiment moche.

23
Martin Ba

Cela devrait fonctionner:

ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
Assembly asm = Assembly.GetCallingAssembly();
String path = Path.GetDirectoryName(new Uri(asm.EscapedCodeBase).LocalPath);

string strLog4NetConfigPath = System.IO.Path.Combine(path, "log4net.config");

J'utilise ceci pour pouvoir me connecter à partir des bibliothèques dll en utilisant un fichier log4net.config autonome.

5
mmmmmm

Une autre solution, y compris des chemins complexes:

    public static string GetPath(this Assembly assembly)
    {
        return Path.GetDirectoryName(Assembly.GetFileName());
    }

    public static string GetFileName(this Assembly assembly)
    {
        return Assembly.CodeBase.GetPathFromUri();
    }

    public static string GetPathFromUri(this string uriString)
    {
        var uri = new Uri(Uri.EscapeUriString(uriString));
        return String.Format("{0}{1}", Uri.UnescapeDataString(uri.PathAndQuery), Uri.UnescapeDataString(uri.Fragment));
    }

et tests:

    [Test]
    public void GetPathFromUriTest()
    {
        Assert.AreEqual(@"C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release", @"file:///C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release".GetPathFromUri());
        Assert.AreEqual(@"C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release",  @"file://C:/Test/Space( )(h#)(p%20){[a&],t@,p%,+}.,/Release".GetPathFromUri());
    }

    [Test]
    public void AssemblyPathTest()
    {
        var asm = Assembly.GetExecutingAssembly();

        var path = asm.GetPath();
        var file = asm.GetFileName();

        Assert.IsNotEmpty(path);
        Assert.IsNotEmpty(file);

        Assert.That(File     .Exists(file));
        Assert.That(Directory.Exists(path));
    }
3
ili

Puisque vous avez marqué cette question NUnit, vous pouvez également utiliser AssemblyHelper.GetDirectoryName pour obtenir le répertoire d'origine de l'assembly en cours d'exécution:

using System.Reflection;
using NUnit.Framework.Internal;
...
string path = AssemblyHelper.GetDirectoryName(Assembly.GetExecutingAssembly())
2
wezzix