Path.Combine pour les URL?

1244

Path.Combine est pratique, mais existe-t-il une fonction similaire dans le framework .NET pour les URL ?

Je recherche une syntaxe comme celle-ci:

Url.Combine("http://MyUrl.com/", "/Images/Image.jpg")

qui retournerait:

"http://MyUrl.com/Images/Image.jpg"

Brian MacKay
la source
14
Flurl inclut une Url.Combineméthode qui fait exactement cela.
Todd Menier
2
En fait, le // est géré par le routage du site Web ou du serveur et non par le navigateur. Il enverra ce que vous mettez dans la barre d'adresse. C'est pourquoi nous rencontrons des problèmes lorsque nous tapons htp: // au lieu de http: // Ainsi, // peut causer des problèmes majeurs sur certains sites. J'écris un .dll pour un robot qui gère un site Web particulier qui jette un 404 si vous avez // dans l'url.
Dave Gordon

Réponses:

73

Il y a un commentaire de Todd Menier ci - dessus selon lequel Flurl inclut un Url.Combine.

Plus de détails:

Url.Combine est essentiellement un Path.Combine pour les URL, assurant un et un seul caractère séparateur entre les parties:

var url = Url.Combine(
    "http://MyUrl.com/",
    "/too/", "/many/", "/slashes/",
    "too", "few?",
    "x=1", "y=2"
// result: "http://www.MyUrl.com/too/many/slashes/too/few?x=1&y=2" 

Obtenez Flurl.Http sur NuGet :

PM> Install-Package Flurl.Http

Ou obtenez le générateur d'URL autonome sans les fonctionnalités HTTP:

PM> Flurl du package d'installation

Michael Freidgeim
la source
4
Eh bien, cette question reçoit beaucoup de trafic, et la réponse avec plus de 1000 votes positifs ne fonctionne pas dans tous les cas. Des années plus tard, j'utilise Flurl pour cela, donc j'accepte celui-ci. Il semble fonctionner dans tous les cas que j'ai rencontrés. Si les gens ne veulent pas prendre une dépendance, j'ai posté une réponse qui fonctionne également très bien.
Brian MacKay
et si vous n'utilisez pas Flurlet préférez une version allégée, github.com/jean-lourenco/UrlCombine
lizzy91
1157

Uri a un constructeur qui devrait le faire pour vous: new Uri(Uri baseUri, string relativeUri)

Voici un exemple:

Uri baseUri = new Uri("http://www.contoso.com");
Uri myUri = new Uri(baseUri, "catalog/shownew.htm");

Note de l'éditeur: Attention, cette méthode ne fonctionne pas comme prévu. Il peut couper une partie de baseUri dans certains cas. Voir les commentaires et autres réponses.

Joel Beckham
la source
369
J'aime l'utilisation de la classe Uri, malheureusement elle ne se comportera pas comme Path.Combine comme l'OP l'a demandé. Par exemple new Uri (new Uri (" test.com/mydirectory/" ), "/helloworld.aspx"). ToString () vous donne " test.com/helloworld.aspx "; ce qui serait incorrect si nous voulions un résultat de style Path.Combine.
Docteur Jones du
195
Tout est dans les barres obliques. Si la partie de chemin relatif commence par une barre oblique, elle se comporte comme vous l'avez décrit. Mais, si vous omettez la barre oblique, cela fonctionne comme vous vous y attendez (notez la barre oblique manquante sur le deuxième paramètre): nouvel Uri (nouvel Uri (" test.com/mydirectory/" ), "helloworld.aspx" ) .ToString () donne " test.com/mydirectory/helloworld.aspx ". Path.Combine se comporte de la même manière. Si le paramètre de chemin relatif commence par une barre oblique, il renvoie uniquement le chemin relatif et ne les combine pas.
Joel Beckham
70
Si votre baseUri se trouvait être "test.com/mydirectory/mysubdirectory" alors le résultat serait "test.com/mydirectory/helloworld.aspx" au lieu de "test.com/mydirectory/mysubdirectory/helloworld.aspx". La différence subtile est l'absence de barre oblique de fin sur le premier paramètre. Je suis tout à fait d'accord pour utiliser les méthodes de framework existantes, si je dois déjà avoir la barre oblique de fin, alors je pense que faire partUrl1 + partUrl2 sent beaucoup moins - j'aurais pu potentiellement chasser cette barre oblique de fin pendant un bon moment tout pour le souci de ne pas faire de concaténation de chaîne.
Carl
64
La seule raison pour laquelle je veux une méthode de combinaison d'URI est que je n'ai pas à vérifier la barre oblique de fin. Request.ApplicationPath est '/' si votre application est à la racine, mais '/ foo' si ce n'est pas le cas.
nickd
24
Je -1 cette réponse car cela ne répond pas au problème. Lorsque vous souhaitez combiner une URL, comme lorsque vous souhaitez utiliser Path.Combine, vous ne voulez pas vous soucier de la fin /. et avec cela, vous devez faire attention. Je préfère la solution de Brian MacKay ou mdsharpe ci
Baptiste Pernet
161

Cela peut être une solution suffisamment simple:

public static string Combine(string uri1, string uri2)
{
    uri1 = uri1.TrimEnd('/');
    uri2 = uri2.TrimStart('/');
    return string.Format("{0}/{1}", uri1, uri2);
}
Matthew Sharpe
la source
7
+1: Bien que cela ne gère pas les chemins de style relatif (../../wthing.html), j'aime celui-ci pour sa simplicité. J'ajouterais également des trims pour le caractère '\'.
Brian MacKay
3
Voir ma réponse pour une version plus complète de ceci.
Brian MacKay
149

Vous utilisez Uri.TryCreate( ... ):

Uri result = null;

if (Uri.TryCreate(new Uri("http://msdn.microsoft.com/en-us/library/"), "/en-us/library/system.uri.trycreate.aspx", out result))
{
    Console.WriteLine(result);
}

Reviendra:

http://msdn.microsoft.com/en-us/library/system.uri.trycreate.aspx

Ryan Cook
la source
53
+1: C'est bien, même si j'ai un problème irrationnel avec le paramètre de sortie. ;)
Brian MacKay
10
@Brian: si cela aide, toutes les méthodes TryXXX ( int.TryParse, DateTime.TryParseExact) ont ce paramètre de sortie pour faciliter leur utilisation dans une instruction if. Btw, vous n'avez pas à initialiser la variable comme Ryan l'a fait dans cet exemple.
Abel
41
Cette réponse souffre du même problème que Joel : rejoindre test.com/mydirectory/et /helloworld.aspxse traduira par test.com/helloworld.aspxce qui n'est apparemment pas ce que vous voulez.
Matt Kocaj
3
Salut, cela a échoué pour ce qui suit: if (Uri.TryCreate (new Uri (" localhost / MyService /" ), "/ Event / SomeMethod? Abc = 123", out result)) {Console.WriteLine (result); } Il me montre le résultat comme: localhost / Event / SomeMethod? Abc = 123 Remarque: "http: //" est remplacé à partir de la base Uri ici par stackoverflow
Faisal Mq
3
@FaisalMq C'est le comportement correct, car vous avez passé un deuxième paramètre relatif à la racine. Si vous aviez omis le premier / sur le deuxième paramètre, vous auriez obtenu le résultat que vous attendiez.
Tom Lint,
127

Il y a déjà d'excellentes réponses ici. Basé sur la suggestion de mdsharpe, voici une méthode d'extension qui peut facilement être utilisée lorsque vous souhaitez gérer des instances Uri:

using System;
using System.Linq;

public static class UriExtensions
{
    public static Uri Append(this Uri uri, params string[] paths)
    {
        return new Uri(paths.Aggregate(uri.AbsoluteUri, (current, path) => string.Format("{0}/{1}", current.TrimEnd('/'), path.TrimStart('/'))));
    }
}

Et exemple d'utilisation:

var url = new Uri("http://example.com/subpath/").Append("/part1/", "part2").AbsoluteUri;

Cela produira http://example.com/subpath/part1/part2

Ales Potocnik Hahonina
la source
2
Cette solution rend triviale l'écriture d'une méthode statique UriUtils.Combine ("base url", "part1", "part2", ...) qui est très similaire à Path.Combine (). Agréable!
angularsen
Pour prendre en charge les URI relatifs, j'ai dû utiliser ToString () au lieu d'AbsoluteUri et UriKind.AbsoluteOrRelative dans le constructeur Uri.
angularsen
Merci pour le conseil sur les Uris relatifs. Malheureusement, Uri ne facilite pas le traitement des chemins relatifs, car il y a toujours un peu de boue avec Request.ApplicationPath impliqué. Peut-être pourriez-vous également essayer d'utiliser un nouvel Uri (HttpContext.Current.Request.ApplicationPath) comme base et simplement appeler Append dessus? Cela vous donnera des chemins absolus mais devrait fonctionner n'importe où dans la structure du site.
Ales Potocnik Hahonina
Génial. Heureux que cela ait aidé quelqu'un d'autre. J'utilise cela depuis un certain temps maintenant et je n'ai eu aucun problème.
Ales Potocnik Hahonina
J'ai également ajouté vérifier si l'un des chemins à ajouter n'est pas une chaîne vide ou vide.
n.podbielski
92

La réponse de Ryan Cook est proche de ce que je recherche et peut être plus appropriée pour d'autres développeurs. Cependant, il ajoute http: // au début de la chaîne et en général, il fait un peu plus de mise en forme que je ne le suis.

De plus, pour mes cas d'utilisation, la résolution de chemins relatifs n'est pas importante.

La réponse de mdsharp contient également le germe d'une bonne idée, bien que cette implémentation réelle ait besoin de quelques détails supplémentaires pour être complète. Ceci est une tentative de l'étoffer (et je l'utilise en production):

C #

public string UrlCombine(string url1, string url2)
{
    if (url1.Length == 0) {
        return url2;
    }

    if (url2.Length == 0) {
        return url1;
    }

    url1 = url1.TrimEnd('/', '\\');
    url2 = url2.TrimStart('/', '\\');

    return string.Format("{0}/{1}", url1, url2);
}

VB.NET

Public Function UrlCombine(ByVal url1 As String, ByVal url2 As String) As String
    If url1.Length = 0 Then
        Return url2
    End If

    If url2.Length = 0 Then
        Return url1
    End If

    url1 = url1.TrimEnd("/"c, "\"c)
    url2 = url2.TrimStart("/"c, "\"c)

    Return String.Format("{0}/{1}", url1, url2)
End Function

Ce code passe le test suivant, qui se trouve être en VB:

<TestMethod()> Public Sub UrlCombineTest()
    Dim target As StringHelpers = New StringHelpers()

    Assert.IsTrue(target.UrlCombine("test1", "test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("test1/", "test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("test1", "/test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("test1/", "/test2") = "test1/test2")
    Assert.IsTrue(target.UrlCombine("/test1/", "/test2/") = "/test1/test2/")
    Assert.IsTrue(target.UrlCombine("", "/test2/") = "/test2/")
    Assert.IsTrue(target.UrlCombine("/test1/", "") = "/test1/")
End Sub
Brian MacKay
la source
4
En parlant de détails: qu'en est-il de l'obligation ArgumentNullException("url1")si l'argument est Nothing? Désolé, juste difficile ;-). Notez qu'une barre oblique inverse n'a rien à voir dans un URI (et si elle est là, elle ne doit pas être coupée), vous pouvez donc la supprimer de votre TrimXXX.
Abel
4
vous pouvez utiliser la chaîne params [] et les joindre récursivement pour autoriser plus de 2 combinaisons
Jaider
4
Je souhaite que ce soit dans la bibliothèque de classes de base comme Path.Combine.
Uriah Blatherwick du
1
@MarkHurd J'ai à nouveau édité le code, de sorte qu'il soit identique au comportement en C # et également syntaxiquement équivalent.
JJS
1
@BrianMacKay je l'ai cassé, markhurd a souligné mon erreur et a reculé, j'ai mis à jour à nouveau ... cheers
JJS
36

Path.Combine ne fonctionne pas pour moi car il peut y avoir des caractères comme "|" dans les arguments QueryString et donc l'URL, ce qui entraînera une ArgumentException.

J'ai d'abord essayé la nouvelle Uri(Uri baseUri, string relativeUri)approche, qui a échoué pour moi à cause d'URI comme http://www.mediawiki.org/wiki/Special:SpecialPages:

new Uri(new Uri("http://www.mediawiki.org/wiki/"), "Special:SpecialPages")

se traduira par Special: SpecialPages, à cause des deux points après Special cela dénote un schéma.

J'ai donc finalement dû prendre la route mdsharpe / Brian MacKays et la développer un peu plus pour travailler avec plusieurs parties URI:

public static string CombineUri(params string[] uriParts)
{
    string uri = string.Empty;
    if (uriParts != null && uriParts.Length > 0)
    {
        char[] trims = new char[] { '\\', '/' };
        uri = (uriParts[0] ?? string.Empty).TrimEnd(trims);
        for (int i = 1; i < uriParts.Length; i++)
        {
            uri = string.Format("{0}/{1}", uri.TrimEnd(trims), (uriParts[i] ?? string.Empty).TrimStart(trims));
        }
    }
    return uri;
}

Usage: CombineUri("http://www.mediawiki.org/", "wiki", "Special:SpecialPages")

Mike Fuchs
la source
1
+1: Maintenant, nous parlons ... Je vais essayer cela. Cela pourrait même devenir la nouvelle réponse acceptée. Après avoir essayé la nouvelle méthode Uri (), je ne l'aime vraiment pas. Trop finnicky.
Brian MacKay
C'est exactement ce dont j'avais besoin! Je n'étais pas fan de devoir faire attention où je mettais des barres obliques, etc ...
Gromer
+1 pour rouler dans la vérification nulle afin qu'il ne explose pas.
NightOwl888
Count () doit être de longueur afin que vous n'ayez pas besoin d'inclure Linq dans votre bibliothèque juste pour cela.
PRMan
C'était exactement ce que je cherchais.
ThePeter
34

Basé sur l'exemple d' URL vous avez fourni, je suppose que vous souhaitez combiner des URL relatives à votre site.

Sur la base de cette hypothèse, je proposerai cette solution comme la réponse la plus appropriée à votre question qui était: "Path.Combine est pratique, y a-t-il une fonction similaire dans le cadre des URL?"

Puisqu'il y a une fonction similaire dans le cadre des URL, je propose que la bonne soit: "VirtualPathUtility.Combine". Voici le lien de référence MSDN: VirtualPathUtility.Combine, méthode

Il y a une mise en garde: je pense que cela ne fonctionne que pour les URL relatives à votre site (c'est-à-dire que vous ne pouvez pas l'utiliser pour générer des liens vers un autre site Web. Par exemple, var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).

Jeronimo Colon III
la source
+1 car il est proche de ce que je recherche, bien que ce serait idéal s'il fonctionnait pour n'importe quelle ancienne URL. Je double cela deviendra beaucoup plus élégant que ce que mdsharpe proposait.
Brian MacKay
2
La mise en garde est correcte, elle ne peut pas fonctionner avec des uris absolus et le résultat est toujours relatif depuis la racine. Mais il a un avantage supplémentaire, il traite le tilde, comme avec "~ /". Cela en fait un raccourci Server.MapPathet une combinaison.
Abel
25
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
JeremyWeir
la source
12
path.Replace(Path.DirectorySeparatorChar, '/');
Jaider
5
path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
SliverNinja - MSFT
1
Pour le faire fonctionner, vous devez supprimer le premier / dans le deuxième argument, c'est-à-dire "/ Images" - / Path.Combine (" Http://MyUrl.com ", "Images / Image.jpg")
Per G
8
@SliverNinja Ce n'est pas correct La valeur de ce champ est une barre oblique inverse ('\') sous UNIX et une barre oblique ('/') sous Windows et Macintosh. Lorsque vous utilisez Mono sur un système Linux, vous obtenez le mauvais séparateur.
user247702
6
Tous ceux qui geeking sur le séparateur de répertoire oublient que les chaînes pourraient provenir d'un système d'exploitation différent de celui que vous utilisez actuellement. Remplacez simplement la barre oblique inverse par une barre oblique et vous êtes couvert.
JeremyWeir
17

Je viens de mettre en place une petite méthode d'extension:

public static string UriCombine (this string val, string append)
        {
            if (String.IsNullOrEmpty(val)) return append;
            if (String.IsNullOrEmpty(append)) return val;
            return val.TrimEnd('/') + "/" + append.TrimStart('/');
        }

Il peut être utilisé comme ceci:

"www.example.com/".UriCombine("/images").UriCombine("first.jpeg");
urza
la source
12

Exemple plein d'esprit, Ryan, pour terminer avec un lien vers la fonction. Bien joué.

Une recommandation Brian: si vous encapsulez ce code dans une fonction, vous souhaiterez peut-être utiliser un UriBuilder pour encapsuler l'URL de base avant l'appel TryCreate.

Sinon, l'URL de base DOIT inclure le schéma (où UriBuilder supposera http: //). Juste une pensée:

public string CombineUrl(string baseUrl, string relativeUrl) {
    UriBuilder baseUri = new UriBuilder(baseUrl);
    Uri newUri;

    if (Uri.TryCreate(baseUri.Uri, relativeUrl, out newUri))
        return newUri.ToString();
    else
        throw new ArgumentException("Unable to combine specified url values");
}
mtazva
la source
10

Un moyen simple de les combiner et de vous assurer qu'elles sont toujours correctes est:

string.Format("{0}/{1}", Url1.Trim('/'), Url2);
Alex
la source
+1, bien que cela soit très similaire à la réponse de mdsharpe, que j'ai amélioré dans ma réponse. Cette version fonctionne très bien sauf si Url2 commence par / ou \, ou Url1 se termine accidentellement par \, ou si l'une est vide! :)
Brian MacKay
9

La combinaison de plusieurs parties d'une URL peut être un peu délicate. Vous pouvez utiliser le constructeur à deux paramètres Uri(baseUri, relativeUri)ou utiliser la Uri.TryCreate()fonction utilitaire.

Dans les deux cas, vous pourriez finir par renvoyer un résultat incorrect car ces méthodes continuent de tronquer les parties relatives du premier paramètre baseUri, c'est-à-dire de quelque chose comme http://google.com/some/thingà http://google.com.

Pour pouvoir combiner plusieurs parties en une URL finale, vous pouvez copier les deux fonctions ci-dessous:

    public static string Combine(params string[] parts)
    {
        if (parts == null || parts.Length == 0) return string.Empty;

        var urlBuilder = new StringBuilder();
        foreach (var part in parts)
        {
            var tempUrl = tryCreateRelativeOrAbsolute(part);
            urlBuilder.Append(tempUrl);
        }
        return VirtualPathUtility.RemoveTrailingSlash(urlBuilder.ToString());
    }

    private static string tryCreateRelativeOrAbsolute(string s)
    {
        System.Uri uri;
        System.Uri.TryCreate(s, UriKind.RelativeOrAbsolute, out uri);
        string tempUrl = VirtualPathUtility.AppendTrailingSlash(uri.ToString());
        return tempUrl;
    }

Le code complet avec des tests unitaires pour démontrer l'utilisation peut être trouvé à https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs

J'ai des tests unitaires pour couvrir les trois cas les plus courants:

Entrez la description de l'image ici

Believe2014
la source
2
Ça me va plutôt bien. Bien que vous puissiez remplacer la boucle I par une boucle foreach pour une meilleure clarté.
Chris Marisic
Merci Chris. Je viens de changer mon code pour utiliser Foreach.
Believe2014
1
+1 pour tout l'effort supplémentaire. Je dois maintenir un peu cette question pour certaines des réponses les plus votées, vous avez jeté le gant. ;)
Brian MacKay
Désolé, mais pas assez. Fonctionne dans les quelques cas que vous montrez mais loin d'être utilisable dans toutes les combinaisons. Par exemple, les deux points sur le chemin causeront du tort.
Gábor
Pouvez-vous donner un exemple de ce que vous voulez dire? Je serai heureux de résoudre le problème et d'aider les prochains utilisateurs.
Believe2014
7

J'ai trouvé que ça UriBuilderfonctionnait très bien pour ce genre de chose:

UriBuilder urlb = new UriBuilder("http", _serverAddress, _webPort, _filePath);
Uri url = urlb.Uri;
return url.AbsoluteUri;

Voir Classe UriBuilder - MSDN pour plus de constructeurs et de documentation.

javajavajavajavajava
la source
4

Voici la méthode UrlUtility.Combine de Microsoft (OfficeDev PnP) :

    const char PATH_DELIMITER = '/';

    /// <summary>
    /// Combines a path and a relative path.
    /// </summary>
    /// <param name="path"></param>
    /// <param name="relative"></param>
    /// <returns></returns>
    public static string Combine(string path, string relative) 
    {
        if(relative == null)
            relative = String.Empty;

        if(path == null)
            path = String.Empty;

        if(relative.Length == 0 && path.Length == 0)
            return String.Empty;

        if(relative.Length == 0)
            return path;

        if(path.Length == 0)
            return relative;

        path = path.Replace('\\', PATH_DELIMITER);
        relative = relative.Replace('\\', PATH_DELIMITER);

        return path.TrimEnd(PATH_DELIMITER) + PATH_DELIMITER + relative.TrimStart(PATH_DELIMITER);
    }

Source: GitHub

Chris Marisic
la source
Il semble que ce soit pour des chemins plutôt que pour des URL.
Brian MacKay
@BrianMacKay Convenu qu'il lui ressemble, mais il est de la classe UrlUtility et utilisé dans le contexte de la combinaison des URL
2
Modifié pour clarifier à quelle classe il appartient
Soyez prudent lorsque vous utilisez cette classe, le reste de la classe contient des artefacts spécifiques à SharePoint.
Harry Berry
4

Je trouve les éléments suivants utiles et présente les caractéristiques suivantes:

  • Lance un espace nul ou blanc
  • Prend plusieurs paramsparamètres pour plusieurs segments d'URL
  • jette sur vide ou vide

Classe

public static class UrlPath
{
   private static string InternalCombine(string source, string dest)
   {
      if (string.IsNullOrWhiteSpace(source))
         throw new ArgumentException("Cannot be null or white space", nameof(source));

      if (string.IsNullOrWhiteSpace(dest))
         throw new ArgumentException("Cannot be null or white space", nameof(dest));

      return $"{source.TrimEnd('/', '\\')}/{dest.TrimStart('/', '\\')}";
   }

   public static string Combine(string source, params string[] args) 
       => args.Aggregate(source, InternalCombine);
}

Les tests

UrlPath.Combine("test1", "test2");
UrlPath.Combine("test1//", "test2");
UrlPath.Combine("test1", "/test2");

// Result = test1/test2

UrlPath.Combine(@"test1\/\/\/", @"\/\/\\\\\//test2", @"\/\/\\\\\//test3\") ;

// Result = test1/test2/test3

UrlPath.Combine("/test1/", "/test2/", null);
UrlPath.Combine("", "/test2/");
UrlPath.Combine("/test1/", null);

// Throws an ArgumentException
Le général
la source
@PeterMortensen merci pour l'édition
TheGeneral
Quelques problèmes avec les tests: // Resultat = test1 / test2 / test3 \ pour le 4e et le dernier des tests de lancers donne ArgumentNullException au lieu d'ArgumentException
Moriya
3

Ma solution générique:

public static string Combine(params string[] uriParts)
{
    string uri = string.Empty;
    if (uriParts != null && uriParts.Any())
    {
        char[] trims = new char[] { '\\', '/' };
        uri = (uriParts[0] ?? string.Empty).TrimEnd(trims);

        for (int i = 1; i < uriParts.Length; i++)
        {
            uri = string.Format("{0}/{1}", uri.TrimEnd(trims), (uriParts[i] ?? string.Empty).TrimStart(trims));
        }
    }

    return uri;
}
Alex Titarenko
la source
Cette méthode d'assistance est très flexible et fonctionne bien dans de nombreux cas d'utilisation différents. Je vous remercie!
Shiva
3

J'ai créé cette fonction qui vous facilitera la vie:

    /// <summary>
    /// The ultimate Path combiner of all time
    /// </summary>
    /// <param name="IsURL">
    /// true - if the paths are Internet URLs, false - if the paths are local URLs, this is very important as this will be used to decide which separator will be used.
    /// </param>
    /// <param name="IsRelative">Just adds the separator at the beginning</param>
    /// <param name="IsFixInternal">Fix the paths from within (by removing duplicate separators and correcting the separators)</param>
    /// <param name="parts">The paths to combine</param>
    /// <returns>the combined path</returns>
    public static string PathCombine(bool IsURL , bool IsRelative , bool IsFixInternal , params string[] parts)
    {
        if (parts == null || parts.Length == 0) return string.Empty;
        char separator = IsURL ? '/' : '\\';

        if (parts.Length == 1 && IsFixInternal)
        {
            string validsingle;
            if (IsURL)
            {
                validsingle = parts[0].Replace('\\' , '/');
            }
            else
            {
                validsingle = parts[0].Replace('/' , '\\');
            }
            validsingle = validsingle.Trim(separator);
            return (IsRelative ? separator.ToString() : string.Empty) + validsingle;
        }

        string final = parts
            .Aggregate
            (
            (string first , string second) =>
            {
                string validfirst;
                string validsecond;
                if (IsURL)
                {
                    validfirst = first.Replace('\\' , '/');
                    validsecond = second.Replace('\\' , '/');
                }
                else
                {
                    validfirst = first.Replace('/' , '\\');
                    validsecond = second.Replace('/' , '\\');
                }
                var prefix = string.Empty;
                if (IsFixInternal)
                {
                    if (IsURL)
                    {
                        if (validfirst.Contains("://"))
                        {
                            var tofix = validfirst.Substring(validfirst.IndexOf("://") + 3);
                            prefix = validfirst.Replace(tofix , string.Empty).TrimStart(separator);

                            var tofixlist = tofix.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);

                            validfirst = separator + string.Join(separator.ToString() , tofixlist);
                        }
                        else
                        {
                            var firstlist = validfirst.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);
                            validfirst = string.Join(separator.ToString() , firstlist);
                        }

                        var secondlist = validsecond.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);
                        validsecond = string.Join(separator.ToString() , secondlist);
                    }
                    else
                    {
                        var firstlist = validfirst.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);
                        var secondlist = validsecond.Split(new[] { separator } , StringSplitOptions.RemoveEmptyEntries);

                        validfirst = string.Join(separator.ToString() , firstlist);
                        validsecond = string.Join(separator.ToString() , secondlist);
                    }
                }
                return prefix + validfirst.Trim(separator) + separator + validsecond.Trim(separator);
            }
            );
        return (IsRelative ? separator.ToString() : string.Empty) + final;
    }

Cela fonctionne pour les URL ainsi que pour les chemins normaux.

Usage:

    // Fixes internal paths
    Console.WriteLine(PathCombine(true , true , true , @"\/\/folder 1\/\/\/\\/\folder2\///folder3\\/" , @"/\somefile.ext\/\//\"));
    // Result: /folder 1/folder2/folder3/somefile.ext

    // Doesn't fix internal paths
    Console.WriteLine(PathCombine(true , true , false , @"\/\/folder 1\/\/\/\\/\folder2\///folder3\\/" , @"/\somefile.ext\/\//\"));
    //result : /folder 1//////////folder2////folder3/somefile.ext

    // Don't worry about URL prefixes when fixing internal paths
    Console.WriteLine(PathCombine(true , false , true , @"/\/\/https:/\/\/\lul.com\/\/\/\\/\folder2\///folder3\\/" , @"/\somefile.ext\/\//\"));
    // Result: https://lul.com/folder2/folder3/somefile.ext

    Console.WriteLine(PathCombine(false , true , true , @"../../../\\..\...\./../somepath" , @"anotherpath"));
    // Result: \..\..\..\..\...\.\..\somepath\anotherpath
bigworld12
la source
3

Pourquoi ne pas simplement utiliser ce qui suit.

System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")
Andreas
la source
Je cherchais la version PowerShell de ce qui serait: [System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")mais cela échoue avec raison: /Images/Image.jpg. Retirez le /du deuxième sous-chemin et cela fonctionne:[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
Underverse
Belle idée, mais elle échoue, lorsque l'un des paramètres est nul.
pholpar
2

Règles lors de la combinaison d'URL avec un URI

Pour éviter un comportement étrange, il y a une règle à suivre:

  • Le chemin (répertoire) doit se terminer par '/'. Si le chemin se termine sans '/', la dernière partie est traitée comme un nom de fichier et elle sera concaténée lors de la tentative de combinaison avec la partie URL suivante.
  • Il y a une exception: l'adresse URL de base (sans les informations de répertoire) ne doit pas se terminer par '/'
  • la partie chemin ne doit pas commencer par '/'. S'il commence par '/', toutes les informations relatives existantes de l'URL sont supprimées ... l'ajout d'un string.Emptychemin de partie supprimera également le répertoire relatif de l'URL!

Si vous suivez les règles ci-dessus, vous pouvez combiner des URL avec le code ci-dessous. Selon votre situation, vous pouvez ajouter plusieurs parties de «répertoire» à l'URL ...

        var pathParts = new string[] { destinationBaseUrl, destinationFolderUrl, fileName };

        var destination = pathParts.Aggregate((left, right) =>
        {
            if (string.IsNullOrWhiteSpace(right))
                return left;

            return new Uri(new Uri(left), right).ToString();
        });
baHI
la source
2

Si vous ne souhaitez pas ajouter une dépendance tierce telle que Flurl ou créer une méthode d'extension personnalisée, dans ASP.NET Core (également disponible dans Microsoft.Owin), vous pouvez utiliser PathStringce qui est destiné à la création d'URI chemins. Vous pouvez ensuite créer votre URI complet en utilisant une combinaison de ceci, Uriet UriBuilder.

Dans ce cas, ce serait:

new Uri(new UriBuilder("http", "MyUrl.com").Uri, new PathString("/Images").Add("/Image.jpg").ToString())

Cela vous donne toutes les parties constitutives sans avoir à spécifier les séparateurs dans l'URL de base. Malheureusement, PathStringrequiert que /soit ajouté à chaque chaîne sinon il lance en fait un ArgumentException! Mais au moins, vous pouvez créer votre URI de manière déterministe d'une manière qui est facilement testable par unité.

Néo
la source
2

J'ai donc une autre approche, similaire à tous ceux qui ont utilisé UriBuilder.

Je ne voulais pas diviser ma BaseUrl (qui peut contenir une partie du chemin - par exemple http://mybaseurl.com/dev/ ) comme javajavajavajavajava .

L'extrait suivant montre le code + tests.

Attention: cette solution met en minuscule l'hôte et ajoute un port. Si cela n'est pas souhaité, on peut écrire une représentation sous forme de chaîne en utilisant par exemple la Uripropriété de UriBuilder.

  public class Tests
  {
         public static string CombineUrl (string baseUrl, string path)
         {
           var uriBuilder = new UriBuilder (baseUrl);
           uriBuilder.Path = Path.Combine (uriBuilder.Path, path);
           return uriBuilder.ToString();
         }

         [TestCase("http://MyUrl.com/", "/Images/Image.jpg", "http://myurl.com:80/Images/Image.jpg")]
         [TestCase("http://MyUrl.com/basePath", "/Images/Image.jpg", "http://myurl.com:80/Images/Image.jpg")]
         [TestCase("http://MyUrl.com/basePath", "Images/Image.jpg", "http://myurl.com:80/basePath/Images/Image.jpg")]
         [TestCase("http://MyUrl.com/basePath/", "Images/Image.jpg", "http://myurl.com:80/basePath/Images/Image.jpg")]
         public void Test1 (string baseUrl, string path, string expected)
         {
           var result = CombineUrl (baseUrl, path);

           Assert.That (result, Is.EqualTo (expected));
         }
  }

Testé avec .NET Core 2.1 sur Windows 10.

Pourquoi ça marche?

Même s'il Path.Combinerenverra des barres obliques inverses (au moins sur Windows), l'UriBuilder gère ce cas dans le Setter de Path.

Tiré de https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs (attention à l'appel à string.Replace)

[AllowNull]
public string Path
{
      get
      {
          return _path;
      }
      set
      {
          if ((value == null) || (value.Length == 0))
          {
              value = "/";
          }
          _path = Uri.InternalEscapeString(value.Replace('\\', '/'));
          _changed = true;
      }
 }

Est-ce la meilleure approche?

Certes, cette solution est assez auto-descriptive (du moins à mon avis). Mais vous comptez sur la "fonctionnalité" non documentée (au moins, je n'ai rien trouvé avec une recherche rapide sur Google) de l'API .NET. Cela peut changer avec une future version, veuillez donc couvrir la méthode avec des tests.

Il existe des tests dans https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs ( Path_Get_Set) qui vérifient si le \est correctement transformé.

Note latérale: On pourrait également travailler UriBuilder.Uridirectement avec la propriété, si l'uri sera utilisé pour un System.Urictor.

Tobias Schwarzinger
la source
Il s'agit d'une approche très fiable. Bravo pour le test unitaire !!
aggsol
2

Pour tous ceux qui recherchent une ligne et veulent simplement joindre des parties d'un chemin sans créer de nouvelle méthode ou référencer une nouvelle bibliothèque ou construire une valeur URI et la convertir en chaîne, puis ...

string urlToImage = String.Join("/", "websiteUrl", "folder1", "folder2", "folder3", "item");

C'est assez basique, mais je ne vois pas de quoi vous avez besoin de plus. Si vous avez peur de doubler '/', vous pouvez simplement faire un .Replace("//", "/")après. Si vous avez peur de remplacer le "//" doublé dans "https: //", puis effectuez une jointure, remplacez le "/" doublé, puis rejoignez l'URL du site Web (mais je suis pratiquement sûr que la plupart des navigateurs le feront automatiquement convertir quoi que ce soit avec 'https:' à l'avant pour lire au format correct). Cela ressemblerait à ceci:

string urlToImage = String.Join("/","websiteUrl", String.Join("/", "folder1", "folder2", "folder3", "item").Replace("//","/"));

Il y a beaucoup de réponses ici qui gèreront tout ce qui précède, mais dans mon cas, je n'en avais besoin qu'une seule fois dans un seul endroit et je n'aurais pas besoin de m'y fier fortement. De plus, il est vraiment facile de voir ce qui se passe ici.

Voir: https://docs.microsoft.com/en-us/dotnet/api/system.string.join?view=netframework-4.8

DubDub
la source
1

Utilisation:

    private Uri UriCombine(string path1, string path2, string path3 = "", string path4 = "")
    {
        string path = System.IO.Path.Combine(path1, path2.TrimStart('\\', '/'), path3.TrimStart('\\', '/'), path4.TrimStart('\\', '/'));
        string url = path.Replace('\\','/');
        return new Uri(url);
    }

Il a l'avantage de se comporter exactement comme Path.Combine.

TruthOf42
la source
1

Voici mon approche et je vais l'utiliser pour moi aussi:

public static string UrlCombine(string part1, string part2)
{
    string newPart1 = string.Empty;
    string newPart2 = string.Empty;
    string seperator = "/";

    // If either part1 or part 2 is empty,
    // we don't need to combine with seperator
    if (string.IsNullOrEmpty(part1) || string.IsNullOrEmpty(part2))
    {
        seperator = string.Empty;
    }

    // If part1 is not empty,
    // remove '/' at last
    if (!string.IsNullOrEmpty(part1))
    {
        newPart1 = part1.TrimEnd('/');
    }

    // If part2 is not empty,
    // remove '/' at first
    if (!string.IsNullOrEmpty(part2))
    {
        newPart2 = part2.TrimStart('/');
    }

    // Now finally combine
    return string.Format("{0}{1}{2}", newPart1, seperator, newPart2);
}
Amit Bhagat
la source
Ceci n'est acceptable que pour votre cas. Il y a des cas qui pourraient casser votre code. De plus, vous n'avez pas effectué l'encodage approprié des parties du chemin. Cela pourrait être une énorme vulnérabilité lorsqu'il s'agit d'une attaque de script intersite.
Believe2014
J'accepte vos points. Le code est censé faire simplement une combinaison simple de deux parties d'URL.
Amit Bhagat
1

Utilisez ceci:

public static class WebPath
{
    public static string Combine(params string[] args)
    {
        var prefixAdjusted = args.Select(x => x.StartsWith("/") && !x.StartsWith("http") ? x.Substring(1) : x);
        return string.Join("/", prefixAdjusted);
    }
}
Martin Murphy
la source
Belle touche avec «WebPath». :) Le code peut cependant être inutilement dense - il m'est difficile d'y jeter un coup d'œil et de dire, oui, c'est parfait. Cela me donne envie de voir des tests unitaires. C'est peut-être juste moi!
Brian MacKay
1
x.StartsWith ("/") &&! x.StartsWith ("http") - pourquoi la vérification http? que gagnez-vous?
penguat
Vous ne voulez pas essayer de supprimer la barre oblique si elle commence par http.
Martin Murphy
@BrianMacKay, je ne suis pas sûr qu'un deux lignes justifie un test unitaire, mais si vous le souhaitez, n'hésitez pas à en fournir un. Ce n'est pas comme si j'acceptais des correctifs ou quoi que ce soit, mais n'hésitez pas à modifier la suggestion.
Martin Murphy
1

J'ai trouvé que le Uriconstructeur retourne «\» dans «/». Vous pouvez donc également utiliser Path.Combine, avec le Uriconstructeur.

 Uri baseUri = new Uri("http://MyUrl.com");
 string path = Path.Combine("Images", "Image.jpg");
 Uri myUri = new Uri(baseUri, path);
skippy
la source
1

Pour ce que ça vaut, voici quelques méthodes d'extension. Le premier combinera les chemins et le second ajoute des paramètres à l'URL.

    public static string CombineUrl(this string root, string path, params string[] paths)
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            return root;
        }

        Uri baseUri = new Uri(root);
        Uri combinedPaths = new Uri(baseUri, path);

        foreach (string extendedPath in paths)
        {
           combinedPaths = new Uri(combinedPaths, extendedPath);
        }

        return combinedPaths.AbsoluteUri;
    }

    public static string AddUrlParams(this string url, Dictionary<string, string> parameters)
    {
        if (parameters == null || !parameters.Keys.Any())
        {
            return url;
        }

        var tempUrl = new StringBuilder($"{url}?");
        int count = 0;

        foreach (KeyValuePair<string, string> parameter in parameters)
        {
            if (count > 0)
            {
                tempUrl.Append("&");
            }

            tempUrl.Append($"{WebUtility.UrlEncode(parameter.Key)}={WebUtility.UrlEncode(parameter.Value)}");
            count++;
        }

        return tempUrl.ToString();
    }
LawMan
la source
1

Comme on le trouve dans d'autres réponses, soit nouveau Uri()ou TryCreate()peut faire le tick. Cependant, l'Uri de base doit se terminer par /et le parent ne doit PAS commencer par/ ; sinon il supprimera la partie arrière de l'URL de base

Je pense que c'est mieux de le faire comme méthode d'extension, c'est-à-dire

public static Uri Append(this Uri uri, string relativePath)
{
    var baseUri = uri.AbsoluteUri.EndsWith('/') ? uri : new Uri(uri.AbsoluteUri + '/');
    var relative = relativePath.StartsWith('/') ? relativePath.Substring(1) : relativePath;
    return new Uri(baseUri, relative);
}

et pour l'utiliser:

var baseUri = new Uri("http://test.com/test/");
var combinedUri =  baseUri.Append("/Do/Something");

En termes de performances, cela consomme plus de ressources qu'il n'en a besoin, en raison de la classe Uri qui effectue beaucoup d'analyse et de validation; un profilage très grossier (Debug) a fait un million d'opérations en environ 2 secondes. Cela fonctionnera pour la plupart des scénarios, mais pour être plus efficace, il est préférable de tout manipuler sous forme de chaînes, cela prend 125 millisecondes pour 1 million d'opérations. C'est à dire

public static string Append(this Uri uri, string relativePath)
{
    //avoid the use of Uri as it's not needed, and adds a bit of overhead.
    var absoluteUri = uri.AbsoluteUri; //a calculated property, better cache it
    var baseUri = absoluteUri.EndsWith('/') ? absoluteUri : absoluteUri + '/';
    var relative = relativePath.StartsWith('/') ? relativePath.Substring(1) : relativePath;
    return baseUri + relative;
}

Et si vous souhaitez toujours renvoyer un URI, cela prend environ 600 millisecondes pour 1 million d'opérations.

public static Uri AppendUri(this Uri uri, string relativePath)
{
    //avoid the use of Uri as it's not needed, and adds a bit of overhead.
    var absoluteUri = uri.AbsoluteUri; //a calculated property, better cache it
    var baseUri = absoluteUri.EndsWith('/') ? absoluteUri : absoluteUri + '/';
    var relative = relativePath.StartsWith('/') ? relativePath.Substring(1) : relativePath;
    return new Uri(baseUri + relative);
}

J'espère que ça aide.

Mahmoud Hanafy
la source
1

Je pense que cela devrait vous donner plus de flexibilité car vous pouvez traiter autant de segments de chemin que vous le souhaitez:

public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));
Âge d'or
la source