Path.Combine para URL?

1244

Path.Combine es útil, pero ¿hay una función similar en el marco .NET para las URL ?

Estoy buscando una sintaxis como esta:

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

que volvería:

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

Brian MacKay
fuente
14
Flurl incluye un Url.Combinemétodo que hace exactamente eso.
Todd Menier
2
En realidad, el // es manejado por el enrutamiento del sitio web o servidor y no por el navegador. Enviará lo que pones en la barra de direcciones. Es por eso que tenemos problemas cuando escribimos htp: // en lugar de http: // Por lo tanto, // puede causar problemas importantes en algunos sitios. Estoy escribiendo un .dll para un rastreador que maneja un sitio web en particular que arroja un 404 si tiene // en la url.
Dave Gordon

Respuestas:

73

Hay un comentario de Todd Menier arriba de que Flurl incluye a Url.Combine.

Más detalles:

Url.Combine es básicamente un Path.Combine para URL, asegurando un único carácter separador entre las partes:

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" 

Obtenga Flurl.Http en NuGet :

PM> Install-Package Flurl.Http

O obtenga el creador de URL independiente sin las características HTTP:

PM> Instalar paquete Flurl

Michael Freidgeim
fuente
44
Bueno, esta pregunta recibe mucho tráfico, y la respuesta con más de 1000 votos positivos no funciona en todos los casos. Años más tarde, en realidad uso Flurl para esto, así que estoy aceptando este. Parece funcionar en todos los casos que he encontrado. Si las personas no quieren tomar una dependencia, publiqué una respuesta que también funciona bien.
Brian MacKay
y si no usa Flurly prefiere una versión ligera, github.com/jean-lourenco/UrlCombine
lizzy91
1157

Uri tiene un constructor que debería hacer esto por usted: new Uri(Uri baseUri, string relativeUri)

Aquí hay un ejemplo:

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

Nota del editor: Cuidado, este método no funciona como se esperaba. Puede cortar parte de baseUri en algunos casos. Ver comentarios y otras respuestas.

Joel Beckham
fuente
369
Me gusta el uso de la clase Uri, desafortunadamente no se comportará como Path.Combine como lo solicitó el OP. Por ejemplo, nuevo Uri (nuevo Uri (" test.com/mydirectory/" ), "/helloworld.aspx"). ToString () le ofrece " test.com/helloworld.aspx "; lo cual sería incorrecto si quisiéramos un resultado de estilo Path.Combine.
Doctor Jones
195
Todo está en las barras. Si la parte de ruta relativa comienza con una barra inclinada, entonces se comporta como usted describió. Pero, si deja la barra oblicua, entonces funciona de la manera esperada (tenga en cuenta la barra oblicua faltante en el segundo parámetro): nuevo Uri (nuevo Uri (" test.com/mydirectory/" ), "helloworld.aspx" ) .ToString () da como resultado " test.com/mydirectory/helloworld.aspx ". Path.Combine se comporta de manera similar. Si el parámetro de ruta relativa comienza con una barra diagonal, solo devuelve la ruta relativa y no los combina.
Joel Beckham
70
Si su baseUri fuera "test.com/mydirectory/mysubdirectory", entonces el resultado sería "test.com/mydirectory/helloworld.aspx" en lugar de "test.com/mydirectory/mysubdirectory/helloworld.aspx". La sutil diferencia es la falta de barra diagonal en el primer parámetro. Estoy totalmente de acuerdo con el uso de los métodos de marco existentes, si ya tengo que tener la barra inclinada final allí, entonces creo que hacer partUrl1 + partUrl2 huele mucho menos: podría haber estado persiguiendo esa barra diagonal durante bastante tiempo durante todo un tiempo en aras de no hacer cuerda concat.
Carl
64
La única razón por la que quiero un método de combinación URI es para no tener que verificar la barra diagonal final. Request.ApplicationPath es '/' si su aplicación está en la raíz, pero '/ foo' si no lo está.
nickd
24
I -1 esta respuesta porque esto no responde al problema. Cuando desee combinar la URL, como cuando quiera usar Path.Combine, no querrá preocuparse por el final /. y con esto, tienes que preocuparte. Prefiero la solución de Brian MacKay o mdsharpe arriba
Baptiste Pernet
161

Esta puede ser una solución convenientemente simple:

public static string Combine(string uri1, string uri2)
{
    uri1 = uri1.TrimEnd('/');
    uri2 = uri2.TrimStart('/');
    return string.Format("{0}/{1}", uri1, uri2);
}
Matthew Sharpe
fuente
77
+1: Aunque esto no maneja rutas de estilo relativas (../../whatever.html), me gusta esta por su simplicidad. También agregaría adornos para el carácter '\'.
Brian MacKay
3
Vea mi respuesta para una versión más completa de esto.
Brian MacKay
149

Usted usa 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);
}

Volveremos:

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

Ryan Cook
fuente
53
+1: Esto es bueno, aunque tengo un problema irracional con el parámetro de salida. ;)
Brian MacKay
10
@Brian: si ayuda, todos los métodos TryXXX ( int.TryParse, DateTime.TryParseExact) tienen este parámetro de salida para que sea más fácil usarlos en una declaración if. Por cierto, no tiene que inicializar la variable como lo hizo Ryan en este ejemplo.
Abel
41
Esta respuesta sufre el mismo problema que la de Joel : unirse test.com/mydirectory/y /helloworld.aspxresultará en lo test.com/helloworld.aspxque aparentemente no es lo que quieres.
Matt Kocaj
3
Hola, esto falló por lo siguiente: if (Uri. TryCreate (new Uri (" localhost / MyService /" ), "/ Event / SomeMethod? Abc = 123", out result)) {Console.WriteLine (result); } Me muestra el resultado como: localhost / Event / SomeMethod? Abc = 123 Nota: "http: //" se reemplaza de la base Uri aquí por stackoverflow
Faisal Mq
3
@FaisalMq Este es el comportamiento correcto, ya que pasó un segundo parámetro relativo a la raíz. Si hubiera omitido el inicio / en el segundo parámetro, habría obtenido el resultado que esperaba.
Tom Lint
127

Ya hay algunas respuestas geniales aquí. Basado en la sugerencia de mdsharpe, aquí hay un método de extensión que puede usarse fácilmente cuando desee tratar con instancias de 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('/'))));
    }
}

Y ejemplo de uso:

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

Esto producirá http://example.com/subpath/part1/part2

Ales Potocnik Hahonina
fuente
2
Esta solución hace que sea trivial escribir un método estático UriUtils.Combine ("base url", "part1", "part2", ...) que es muy similar a Path.Combine (). ¡Agradable!
angularsen
Para admitir URI relativos, tuve que usar ToString () en lugar de AbsoluteUri y UriKind.AbsoluteOrRelative en el constructor de Uri.
angularsen
Gracias por el consejo sobre el pariente Uris. Desafortunadamente, Uri no facilita el manejo de rutas relativas, ya que siempre hay algo de burla con Request.ApplicationPath involucrado. ¿Quizás también podría intentar usar un nuevo Uri (HttpContext.Current.Request.ApplicationPath) como base y simplemente llamar a Append en él? Esto le dará rutas absolutas, pero debería funcionar en cualquier lugar dentro de la estructura del sitio.
Ales Potocnik Hahonina
Excelente. Me alegro de que haya ayudado a alguien más. He estado usando esto por algún tiempo y no he tenido ningún problema.
Ales Potocnik Hahonina
También agregué verificar si alguna de las rutas para agregar no es nula ni vacía.
n.podbielski
92

La respuesta de Ryan Cook está cerca de lo que busco y puede ser más apropiada para otros desarrolladores. Sin embargo, agrega http: // al comienzo de la cadena y, en general, formatea un poco más de lo que estoy buscando.

Además, para mis casos de uso, resolver rutas relativas no es importante.

La respuesta de mdsharp también contiene la semilla de una buena idea, aunque esa implementación real necesitaba algunos detalles más para completarse. Este es un intento de desarrollarlo (y lo estoy usando en producción):

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

Este código pasa la siguiente prueba, que está 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
fuente
44
Hablando de detalles: ¿qué pasa con lo obligatorio ArgumentNullException("url1")si el argumento es Nothing? Lo siento, solo soy exigente ;-). Tenga en cuenta que una barra invertida no tiene nada que ver en un URI (y si está allí, no debe recortarse), por lo que puede eliminarla de su TrimXXX.
Abel
44
puede usar la cadena de parámetros [] y unirlos recursivamente para permitir más de 2 combinaciones
Jaider
44
Desearía que esto estuviera en la Biblioteca de la Clase Base como Path.Combine.
Uriah Blatherwick
1
@MarkHurd Volví a editar el código, para que sea conductualmente igual que C #, y también sintácticamente equivalente.
JJS
1
@BrianMacKay Lo rompí, Markhurd señaló mi error y retrocedí, actualicé nuevamente ... saludos
JJS
36

Path.Combine no funciona para mí porque puede haber caracteres como "|" en los argumentos de QueryString y, por lo tanto, en la URL, lo que dará como resultado una excepción ArgumentException.

Primero probé el nuevo Uri(Uri baseUri, string relativeUri)enfoque, que me falló debido a URI como http://www.mediawiki.org/wiki/Special:SpecialPages:

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

resultará en Special: SpecialPages, debido a los dos puntos Specialque indican un esquema.

Así que finalmente tuve que tomar la ruta mdsharpe / Brian MacKays y la desarrollé un poco más para trabajar con múltiples partes 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;
}

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

Mike Fuchs
fuente
1
+1: Ahora estamos hablando ... Voy a probar esto. Esto incluso podría terminar siendo la nueva respuesta aceptada. Después de intentar el nuevo método Uri (), realmente no me gusta. Demasiado quisquilloso.
Brian MacKay
¡Esto es exactamente lo que necesitaba! No era fanático de tener que preocuparme de dónde ponía las barras inclinadas, etc ...
Gromer
+1 por rodar en la comprobación nula para que no explote.
NightOwl888
Count () debe ser Longitud para que no necesite incluir Linq en su biblioteca solo por eso.
PRMan
Esto era exactamente lo que estaba buscando.
ThePeter
34

Según la URL de ejemplo que proporcionó, voy a suponer que desea combinar las URL relacionadas con su sitio.

En base a esta suposición, propondré esta solución como la respuesta más adecuada a su pregunta, que fue: "Path.Combine es útil, ¿hay una función similar en el marco para las URL?"

Dado que existe una función similar en el marco para las URL, propongo que la correcta es: método "VirtualPathUtility.Combine". Aquí está el enlace de referencia de MSDN: VirtualPathUtility.Combine Method

Hay una advertencia: creo que esto solo funciona para las URL relativas a su sitio (es decir, no puede usarlo para generar enlaces a otro sitio web. Por ejemplo var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).

Jerónimo Colón III
fuente
+1 porque está cerca de lo que estoy buscando, aunque sería ideal si funcionara para cualquier URL antigua. Doblarlo será mucho más elegante de lo que propuso mdsharpe.
Brian MacKay
2
La advertencia es correcta, no puede funcionar con uris absolutas y el resultado siempre es relativo desde la raíz. Pero tiene un beneficio adicional, procesa la tilde, como con "~ /". Esto lo convierte en un atajo Server.MapPathy combinación.
Abel
25
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
JeremyWeir
fuente
12
path.Replace(Path.DirectorySeparatorChar, '/');
Jaider
55
path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
SliverNinja - MSFT
1
Para hacerlo funcionar, debe eliminar primero / en el segundo argumento, es decir, "/ Images" - / Path.Combine (" Http://MyUrl.com ", "Images / Image.jpg")
Por G
8
@SliverNinja Eso no es correcto El valor de este campo es una barra diagonal inversa ('\') en UNIX y una barra diagonal ('/') en los sistemas operativos Windows y Macintosh. Al usar Mono en un sistema Linux, obtendría el separador incorrecto.
user247702
66
Todos los que se están escapando en el Separador de directorios están olvidando que las cadenas podrían haber venido de un sistema operativo diferente al que usted tiene ahora. Simplemente reemplace la barra diagonal inversa por barra diagonal y estará cubierto.
JeremyWeir
17

Acabo de armar un pequeño método de extensión:

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('/');
        }

Se puede usar así:

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

Ejemplo ingenioso, Ryan, para terminar con un enlace a la función. Bien hecho.

Una recomendación Brian: si envuelve este código en una función, es posible que desee utilizar un UriBuilder para envolver la URL base antes de la llamada TryCreate.

De lo contrario, la URL base DEBE incluir el esquema (donde UriBuilder asumirá http: //). Solo un pensamiento:

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
fuente
10

Una manera fácil de combinarlos y garantizar que siempre sea correcta es:

string.Format("{0}/{1}", Url1.Trim('/'), Url2);
Alex
fuente
+1, aunque esto es muy similar a la respuesta de mdsharpe, que mejoré en mi respuesta. ¡Esta versión funciona muy bien a menos que Url2 comience con / o \, o Url1 accidentalmente termine en \, o cualquiera de los dos esté vacío! :)
Brian MacKay
9

Combinar varias partes de una URL podría ser un poco complicado. Puede usar el constructor de dos parámetros Uri(baseUri, relativeUri), o puede usar la Uri.TryCreate()función de utilidad.

En cualquier caso, usted podría terminar de devolver un resultado incorrecto debido a que estos métodos siguen truncar las partes relativas fuera del primer parámetro baseUri, es decir, de algo así como http://google.com/some/thinga http://google.com.

Para poder combinar varias partes en una URL final, puede copiar las dos funciones a continuación:

    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;
    }

El código completo con pruebas unitarias para demostrar el uso se puede encontrar en https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs

Tengo pruebas unitarias para cubrir los tres casos más comunes:

Ingrese la descripción de la imagen aquí

Creer2014
fuente
2
Se ve muy bien para mí. Aunque podría reemplazar el bucle I con un bucle foreach para una mejor claridad.
Chris Marisic
Gracias Chris Acabo de cambiar mi código para usar Foreach.
Believe2014
1
+1 por todo el esfuerzo extra. Necesito mantener esta pregunta un poco para algunas de las respuestas más votadas, ustedes han arrojado el guante. ;)
Brian MacKay
Lo siento, pero no lo suficiente. Funciona en los pocos casos que muestra, pero lejos de ser utilizable en todas las combinaciones. Por ejemplo, los dos puntos en el camino causarán daño.
Gábor
¿Puedes dar un ejemplo de lo que quieres decir? Estaré encantado de solucionar el problema y ayudar a los próximos usuarios.
Believe2014
7

Encontré que UriBuilderfuncionó muy bien para este tipo de cosas:

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

Ver Clase UriBuilder - MSDN para más constructores y documentación.

javajavajavajavajava
fuente
4

Aquí está el método 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);
    }

Fuente: GitHub

Chris Marisic
fuente
Parece que esto podría ser para rutas, en lugar de URL.
Brian MacKay
@BrianMacKay Estuvo de acuerdo en que lo parece, pero es de la clase UrlUtility y se usa en el contexto de la combinación de URL
2
Editado para aclarar a qué clase pertenece
Tenga cuidado al usar esta clase, el resto de la clase contiene artefactos específicos de SharePoint.
Harry Berry
4

Encuentro lo siguiente útil y tiene las siguientes características:

  • Lanza en espacios nulos o blancos
  • Toma múltiples paramsparámetros para múltiples segmentos de URL
  • tira en nulo o vacío

Clase

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);
}

Pruebas

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
El general
fuente
@PeterMortensen gracias por la edición
TheGeneral
Algunos problemas con las pruebas: // Resultado = prueba1 / prueba2 / prueba3 \ para la cuarta y la última de las pruebas de lanzamiento da ArgumentNullException en lugar de ArgumentException
Moriya
3

Mi solución genérica:

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
fuente
Este método auxiliar es muy flexible y funciona bien en muchos casos de uso diferentes. ¡Gracias!
Shiva
3

Creé esta función que te facilitará la vida:

    /// <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;
    }

Funciona tanto para URL como para rutas normales.

Uso:

    // 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
fuente
3

¿Por qué no simplemente usar lo siguiente?

System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")
Andreas
fuente
Yo estaba buscando la versión de este PowerShell que serían: [System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")sin embargo, esto no funciona con un resultado de: /Images/Image.jpg. Elimine el /del segundo subPath y funciona:[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
Underverse
Buena idea, pero falla, cuando uno de los parámetros es nulo.
pholpar
2

Reglas al combinar URL con un URI

Para evitar comportamientos extraños hay una regla a seguir:

  • La ruta (directorio) debe terminar con '/'. Si la ruta termina sin '/', la última parte se trata como un nombre de archivo, y se concatenará cuando se intente combinar con la siguiente parte de URL.
  • Hay una excepción: la dirección URL base (sin información del directorio) no necesita terminar con '/'
  • la parte de la ruta no debe comenzar con '/'. Si comienza con '/', string.Emptyse elimina toda la información relativa existente de la URL ... al agregar una ruta parcial, también se eliminará el directorio relativo de la URL.

Si sigue las reglas anteriores, puede combinar las URL con el código a continuación. Dependiendo de su situación, puede agregar múltiples partes de 'directorio' a la 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
fuente
2

Si no desea agregar una dependencia de terceros como Flurl o crear un método de extensión personalizado, en ASP.NET Core (también disponible en Microsoft.Owin), puede usar el PathStringque está destinado a construir URI rutas. Luego puede crear su URI completo usando una combinación de esto, Uriy UriBuilder.

En este caso, sería:

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

Esto le proporciona todas las partes constituyentes sin tener que especificar los separadores en la URL base. Desafortunadamente, PathStringrequiere que /se anteponga a cada cadena; de lo contrario, arroja un ArgumentException! Pero al menos puede construir su URI de manera determinista de una manera que sea fácilmente comprobable por unidad.

Neo
fuente
2

Así que tengo otro enfoque, similar a todos los que usaron UriBuilder.

No quería dividir mi BaseUrl (que puede contener una parte de la ruta, por ejemplo, http://mybaseurl.com/dev/ ) como lo hizo javajavajavajavajava .

El siguiente fragmento muestra el código + Pruebas.

Cuidado: esta solución pone en minúscula el host y agrega un puerto. Si esto no se desea, se puede escribir una representación de cadena, por ejemplo, aprovechando la UriPropiedad 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));
         }
  }

Probado con .NET Core 2.1 en Windows 10.

¿Por qué funciona esto?

Aunque Path.Combinedevolverá las barras invertidas (al menos en Windows), el UriBuilder maneja este caso en el Setter de Path.

Tomado de https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs (importa la llamada a string.Replace)

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

¿Es este el mejor enfoque?

Ciertamente, esta solución es bastante autodescriptiva (al menos en mi opinión). Pero confía en la "característica" indocumentada (al menos no encontré nada con una búsqueda rápida en Google) de la API de .NET. Esto puede cambiar con una versión futura, así que cubra el Método con Pruebas.

Hay pruebas en https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs ( Path_Get_Set) que comprueban si \se transforma correctamente.

Nota al margen: Uno también podría trabajar con la UriBuilder.Uripropiedad directamente, si la uri se usará para un System.Urictor.

Tobias Schwarzinger
fuente
Este es un enfoque muy confiable. ¡Pulgares para la prueba de la unidad!
aggsol
2

Para cualquiera que esté buscando una línea y simplemente quiera unir partes de una ruta sin crear un nuevo método o hacer referencia a una nueva biblioteca o construir un valor de URI y convertirlo en una cadena, entonces ...

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

Es bastante básico, pero no veo qué más necesitas. Si tienes miedo de duplicar '/', entonces simplemente puedes hacer un .Replace("//", "/")después. Si tiene miedo de reemplazar el '//' duplicado en 'https: //', haga una unión, reemplace el '/' duplicado, luego únase a la URL del sitio web (sin embargo, estoy bastante seguro de que la mayoría de los navegadores automáticamente convierta cualquier cosa con 'https:' al frente para leer en el formato correcto). Esto se vería así:

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

Aquí hay muchas respuestas que manejarán todo lo anterior, pero en mi caso, solo lo necesitaba una vez en una ubicación y no necesitaré depender mucho de él. Además, es realmente fácil ver lo que está pasando aquí.

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

DubDub
fuente
1

Utilizar:

    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);
    }

Tiene el beneficio de comportarse exactamente igual Path.Combine.

TruthOf42
fuente
1

Aquí está mi enfoque y lo usaré también para mí:

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
fuente
Esto es aceptable solo para su caso. Hay casos que podrían romper su código. Además, no realizó la codificación adecuada de las partes de la ruta. Esto podría ser una gran vulnerabilidad cuando se trata de ataques de secuencias de comandos en sitios cruzados.
Believe2014
Estoy de acuerdo con tus puntos. Se supone que el código debe hacer una simple combinación de dos partes de URL.
Amit Bhagat
1

Utilizar este:

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
fuente
Buen toque con 'WebPath'. :) Sin embargo, el código puede ser excesivamente denso; es difícil para mí mirar esto y decir, sí, eso es perfecto. Me dan ganas de ver pruebas unitarias. ¡Tal vez solo soy yo!
Brian MacKay
1
x.StartsWith ("/") &&! x.StartsWith ("http"): ¿por qué verificar http? que ganas
pingüino
No desea intentar quitar la barra inclinada si comienza con http.
Martin Murphy
@BrianMacKay, no estoy seguro de que un revestimiento doble justifique una prueba unitaria, pero si lo desea, no dude en proporcionar una. No es que esté aceptando parches ni nada, pero puede editar la sugerencia.
Martin Murphy
1

Descubrí que el Uriconstructor voltea '\' a '/'. Entonces también puedes usar Path.Combine, con el Uriconstructor.

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

Para lo que vale, aquí hay un par de métodos de extensión. El primero combinará rutas y el segundo agregará parámetros a la 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
fuente
1

Como se encuentra en otras respuestas, ya sea nuevo Uri()o TryCreate()puede hacer el tic. Sin embargo, la base Uri tiene que terminar /y el pariente NO debe comenzar con/ ; de lo contrario, eliminará la parte posterior de la URL base

Creo que esto se hace mejor como un método de extensión, es decir

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);
}

y para usarlo:

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

En términos de rendimiento, esto consume más recursos de los que necesita, debido a la clase Uri que analiza y valida mucho; un perfil muy tosco (depuración) realizó un millón de operaciones en aproximadamente 2 segundos. Esto funcionará para la mayoría de los escenarios, sin embargo, para ser más eficiente, es mejor manipular todo como cadenas, esto toma 125 milisegundos para 1 millón de operaciones. Es decir

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;
}

Y si aún desea devolver un URI, se necesitan alrededor de 600 milisegundos para 1 millón de operaciones.

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);
}

Espero que esto ayude.

Mahmoud Hanafy
fuente
1

Creo que esto debería darle más flexibilidad, ya que puede manejar tantos segmentos de ruta como desee:

public static string UrlCombine(this string baseUrl, params string[] segments)
=> string.Join("/", new[] { baseUrl.TrimEnd('/') }.Concat(segments.Select(s => s.Trim('/'))));
Edad de oro
fuente