Path.Combine para URLs?

1244

Path.Combine é útil, mas existe uma função semelhante na estrutura do .NET para URLs ?

Estou procurando uma sintaxe como esta:

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

que retornaria:

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

Brian MacKay
fonte
14
Flurl inclui um Url.Combinemétodo que faz exatamente isso.
21414 Todd Menier
2
Na verdade, o // é tratado pelo roteamento do site ou servidor e não pelo navegador. Ele enviará o que você colocar na barra de endereço. É por isso que obtemos problemas ao digitar htp: // em vez de http: // Portanto, o // pode causar grandes problemas em alguns sites. Estou escrevendo uma DLL para um rastreador que lida com um site específico que lança um 404 se você tiver // no URL.
Dave Gordon

Respostas:

73

um comentário de Todd Menier acima de que Flurl inclui umUrl.Combine .

Mais detalhes:

Url.Combine é basicamente um Path.Combine para URLs, garantindo um e apenas um caractere separador entre 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" 

Obtenha o Flurl.Http no NuGet :

PM> Flurl.Http do pacote de instalação

Ou obtenha o construtor de URL independente sem os recursos HTTP:

PM> Flurl do pacote de instalação

Michael Freidgeim
fonte
4
Bem, essa pergunta recebe muito tráfego e a resposta com mais de 1000 upvotes na verdade não funciona em todos os casos. Anos depois, eu realmente uso o Flurl para isso, então estou aceitando este. Parece funcionar em todos os casos que encontrei. Se as pessoas não querem assumir uma dependência, postei uma resposta que também funciona bem.
Brian MacKay
e se você não uso Flurle seria perfer uma versão leve, github.com/jean-lourenco/UrlCombine
lizzy91
1157

Uri tem um construtor que deve fazer isso por você: new Uri(Uri baseUri, string relativeUri)

Aqui está um exemplo:

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

Nota do editor: Cuidado, este método não funciona conforme o esperado. Pode cortar parte do baseUri em alguns casos. Veja comentários e outras respostas.

Joel Beckham
fonte
369
Gosto do uso da classe Uri, infelizmente ela não se comportará como Path.Combine, como o OP pediu. Por exemplo, novo Uri (novo Uri (" test.com/mydirectory/" ), "/helloworld.aspx"). ToString () fornece " test.com/helloworld.aspx "; o que seria incorreto se desejássemos um resultado no estilo Path.Combine.
Doctor Jones
195
Está tudo nas barras. Se a parte do caminho relativo começar com uma barra, ela se comportará como você descreveu. Mas, se você deixar a barra de fora, ela funcionará da maneira que você esperaria (observe a barra ausente no segundo parâmetro): new Uri (new Uri (nova Uri (" test.com/mydirectory/" ), "helloworld.aspx" ) .ToString () resulta em " test.com/mydirectory/helloworld.aspx ". Path.Combine se comporta de maneira semelhante. Se o parâmetro relativo do caminho começar com uma barra, ele retornará apenas o caminho relativo e não os combinará.
Joel Beckham
70
Se o seu baseUri fosse "test.com/mydirectory/mysubdirectory", o resultado seria "test.com/mydirectory/helloworld.aspx" em vez de "test.com/mydirectory/mysubdirectory/helloworld.aspx". A diferença sutil é a falta de barra final no primeiro parâmetro. Sou a favor do uso de métodos de estrutura existentes, se eu já tiver a barra final, acho que fazer partUrl1 + partUrl2 cheira muito menos - eu poderia estar perseguindo essa barra final por um bom tempo por não fazer concatenação de cordas.
Carl
64
A única razão pela qual eu quero um método de combinação de URI é para que eu não precise verificar a barra final. Request.ApplicationPath é '/' se seu aplicativo estiver na raiz, mas '/ foo' se não estiver.
23411 nickd em
24
Eu -1 esta resposta porque isso não responde ao problema. Quando você deseja combinar o URL, como quando deseja usar o Path.Combine, não deseja se preocupar com o / final. e com isso, você tem que se importar. Eu prefiro solução de Brian MacKay ou mdsharpe acima
Baptiste Pernet
161

Esta pode ser uma solução adequadamente simples:

public static string Combine(string uri1, string uri2)
{
    uri1 = uri1.TrimEnd('/');
    uri2 = uri2.TrimStart('/');
    return string.Format("{0}/{1}", uri1, uri2);
}
Matthew Sharpe
fonte
7
+1: Embora isso não lide com caminhos de estilo relativo (../../whatever.html), eu gosto deste por sua simplicidade. Eu também adicionaria guarnições para o caractere '\'.
Brian MacKay
3
Veja minha resposta para uma versão mais completa disso.
quer
149

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

Retornará:

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

Ryan Cook
fonte
53
+1: Isso é bom, embora eu tenha um problema irracional com o parâmetro de saída. ;)
Brian MacKay
10
@ Brian: se ajudar, todos os métodos TryXXX ( int.TryParse, DateTime.TryParseExact) possuem esse parâmetro de saída para facilitar o uso em uma instrução if. Aliás, você não precisa inicializar a variável como Ryan fez neste exemplo.
Abel
41
Esta resposta sofre o mesmo problema que o de Joel : ingressar test.com/mydirectory/e /helloworld.aspxresultará no test.com/helloworld.aspxque aparentemente não é o que você deseja.
precisa saber é o seguinte
3
Olá, isso falhou no seguinte: if (Uri.TryCreate (new Uri (" localhost / MyService /" ), "/ Event / SomeMethod? Abc = 123", resultado final)) {Console.WriteLine (resultado); } Ele está mostrando me resultar como: localhost / evento / SomeMethod abc = 123? Nota: "http: //" é substituída a partir da base Uri aqui por stackoverflow
Faisal Mq
3
@FaisalMq Esse é o comportamento correto, desde que você passou um segundo parâmetro relativo à raiz. Se você tivesse deixado de fora o líder / o segundo parâmetro, teria obtido o resultado esperado.
precisa saber é o seguinte
127

Já existem ótimas respostas aqui. Com base na sugestão do mdsharpe, aqui está um método de extensão que pode ser facilmente usado quando você deseja lidar com instâncias do 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('/'))));
    }
}

E exemplo de uso:

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

Isso produzirá http://example.com/subpath/part1/part2

Ales Potocnik Hahonina
fonte
2
Essa solução torna trivial escrever um método estático UriUtils.Combine ("base url", "part1", "part2", ...) que é muito semelhante ao Path.Combine (). Agradável!
Angularsen
Para dar suporte aos URIs relativos, tive que usar ToString () em vez de AbsoluteUri e UriKind.AbsoluteOrRelative no construtor Uri.
Angularsen
Obrigado pela dica sobre o Uris relativo. Infelizmente, o Uri não facilita o manuseio de caminhos relativos, pois sempre há algum problema com o Request.ApplicationPath envolvido. Talvez você também possa tentar usar o novo Uri (HttpContext.Current.Request.ApplicationPath) como base e apenas chamar Anexar? Isso fornecerá caminhos absolutos, mas deve funcionar em qualquer lugar da estrutura do site.
Ales Potocnik Hahonina
Ótimo. Ainda bem que ajudou alguém. Estou usando isso há algum tempo e não tive nenhum problema.
Ales Potocnik Hahonina
Também adicionei a verificação se algum dos caminhos a serem anexados não é nulo nem vazio.
precisa saber é o seguinte
92

A resposta de Ryan Cook está próxima do que estou procurando e pode ser mais apropriada para outros desenvolvedores. No entanto, ele adiciona http: // ao início da string e, em geral, faz um pouco mais de formatação do que estou procurando.

Além disso, para meus casos de uso, resolver caminhos relativos não é importante.

A resposta do mdsharp também contém a semente de uma boa idéia, embora essa implementação real precise de mais alguns detalhes para ser concluída. Esta é uma tentativa de detalhar (e estou usando isso na produção):

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

Esse código passa no seguinte teste, que acontece no 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
fonte
4
Falando de detalhes: e o obrigatório ArgumentNullException("url1")se o argumento for Nothing? Desculpe, apenas sendo exigente ;-). Observe que uma barra invertida não tem nada a ver em um URI (e, se houver, não deve ser aparada), para que você possa removê-la do seu TrimXXX.
Abel
4
você pode usar params string [] e de forma recursiva se juntar a eles para permitir que mais de 2 combinações
jaider
4
Eu certamente gostaria que isso estivesse na Biblioteca de Classes Base, como Path.Combine.
Uriah Blatherwick
1
@ MarkHurd Editei o código novamente, para que ele seja comportamentalmente igual ao C # e sintaticamente equivalente também.
JJS 07/07
1
@BrianMacKay i partiu-o, markhurd apontou meu erro e rolou para trás, eu atualizado novamente ... aplausos
JJS
36

Path.Combine não funciona para mim porque pode haver caracteres como "|" nos argumentos QueryString e, portanto, na URL, que resultará em ArgumentException.

Tentei pela primeira vez a nova Uri(Uri baseUri, string relativeUri)abordagem, que falhou por causa de URIs como http://www.mediawiki.org/wiki/Special:SpecialPages:

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

resultará em Special: SpecialPages, por causa dos dois pontos depois Specialque denota um esquema.

Por fim, tive que seguir a rota mdsharpe / Brian MacKays e a desenvolver um pouco mais para trabalhar com várias partes de 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
fonte
1
+1: Agora estamos conversando ... vou tentar isso. Isso pode até acabar sendo a nova resposta aceita. Depois de tentar o novo método Uri (), eu realmente não gosto. Muito finnicky.
precisa
Isso é exatamente o que eu precisava! Não era um fã de ter que cuidar onde eu coloquei arrastando barras, etc ...
Gromer
+1 para rolar na verificação nula, para que não explodir.
precisa
Count () deve ser Length, para que você não precise incluir o Linq na sua biblioteca apenas para isso.
PRMan
Era exatamente isso que eu estava procurando.
ThePeter
34

Com base no URL de amostra você forneceu, suponho que você queira combinar URLs relativos ao seu site.

Com base nessa premissa, proponho esta solução como a resposta mais apropriada à sua pergunta, que foi: "Path.Combine é útil, existe uma função semelhante na estrutura para URLs?"

Como existe uma função semelhante na estrutura dos URLs, proponho que o correto seja: "VirtualPathUtility.Combine". Aqui está o link de referência do MSDN: Método VirtualPathUtility.Combine

Há uma ressalva: acredito que isso funcione apenas para URLs relativos ao seu site (ou seja, você não pode usá-lo para gerar links para outro site. Por exemplo, var url = VirtualPathUtility.Combine("www.google.com", "accounts/widgets");).

Jeronimo Colon III
fonte
+1 porque está próximo do que estou procurando, embora seja ideal se funcione para qualquer URL antigo. Dobro, ficará muito mais elegante do que o que o mdsharpe propôs.
Brian MacKay
2
A ressalva está correta, não pode funcionar com uris absolutos e o resultado é sempre relativo a partir da raiz. Mas tem um benefício adicional, processa o til, como em "~ /". Isso o torna um atalho Server.MapPathe combinação.
Abel
25
Path.Combine("Http://MyUrl.com/", "/Images/Image.jpg").Replace("\\", "/")
JeremyWeir
fonte
12
path.Replace(Path.DirectorySeparatorChar, '/');
Jaider
5
path.Replace(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
SliverNinja - MSFT
1
Para fazê-lo funcionar, você deve remover o primeiro / o segundo argumento, ou seja, "/ Images" - / Path.Combine (" Http://MyUrl.com ", "Images / Image.jpg")
Por G
8
@SliverNinja Isso não está correto O valor desse campo é uma barra invertida ('\') no UNIX e uma barra ('/') nos sistemas operacionais Windows e Macintosh. Ao usar o Mono em um sistema Linux, você obteria o separador errado.
user247702
6
Todos os que estão pesquisando no separador de diretório estão esquecendo que as seqüências de caracteres podem ter vindo de um sistema operacional diferente do que você está agora. Basta substituir a barra invertida por barra e você estará coberto.
31416 JeremyWeir
17

Acabei de montar um pequeno método de extensão:

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

Pode ser usado assim:

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

Exemplo espirituoso, Ryan, para terminar com um link para a função. Bem feito.

Uma recomendação Brian: se você agrupar esse código em uma função, poderá usar um UriBuilder para agrupar o URL base antes da chamada TryCreate.

Caso contrário, o URL base DEVE incluir o esquema (onde o UriBuilder assumirá http: //). Apenas um pensamento:

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

Uma maneira fácil de combiná-los e garantir que esteja sempre correto é:

string.Format("{0}/{1}", Url1.Trim('/'), Url2);
Alex
fonte
+1, embora isso seja muito semelhante à resposta do mdsharpe, que aprimorei na minha resposta. Esta versão funciona muito bem, a menos que o Url2 inicie com / ou \, ou o Url1 termine acidentalmente em \, ou se um estiver vazio! :)
Brian MacKay
9

Combinar várias partes de um URL pode ser um pouco complicado. Você pode usar o construtor de dois parâmetros Uri(baseUri, relativeUri)ou a Uri.TryCreate()função de utilitário.

Em qualquer um dos casos, você pode retornar um resultado incorreto, porque esses métodos continuam truncando as partes relativas do primeiro parâmetro baseUri, ou seja, de algo como http://google.com/some/thingpara http://google.com.

Para poder combinar várias partes em um URL final, você pode copiar as duas funções abaixo:

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

O código completo com testes de unidade para demonstrar o uso pode ser encontrado em https://uricombine.codeplex.com/SourceControl/latest#UriCombine/Uri.cs

Eu tenho testes de unidade para cobrir os três casos mais comuns:

Digite a descrição da imagem aqui

Believe2014
fonte
2
Parece muito bom para mim. Embora você possa substituir o loop I por um loop foreach para melhor clareza.
Chris Marisic
Obrigado Chris. Acabei de alterar meu código para usar o Foreach.
Believe2014
1
+1 para todo o esforço extra. Eu preciso manter essa pergunta um pouco para algumas das respostas mais votadas, você jogou a manopla. ;)
Brian MacKay
Desculpe, mas não o suficiente. Funciona nos poucos casos que você mostra, mas está longe de ser utilizável em todas as combinações. Por exemplo, dois pontos no caminho causarão danos.
Gábor
Você pode dar um exemplo do que você quer dizer? Ficarei feliz em corrigir o problema e ajudar os próximos usuários.
usar o seguinte
7

Eu achei que UriBuilderfuncionou muito bem para esse tipo de coisa:

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

Consulte Classe UriBuilder - MSDN para obter mais construtores e documentação.

javajavajavajavajava
fonte
4

Aqui está o método UrlUtility.Combine da 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);
    }

Fonte: GitHub

Chris Marisic
fonte
Parece que isso pode ser para caminhos, e não para URLs.
Brian MacKay
@BrianMacKay Concordaram que se parece com isto, mas é a partir da classe UrlUtility e utilizada no contexto da combinação de URLs
2
Editado para esclarecer a qual classe pertence #
Tome cuidado ao usar esta classe, o restante da classe contém artefatos específicos do SharePoint.
Harry Berry
4

Acho o seguinte útil e possui os seguintes recursos:

  • Lança espaço nulo ou em branco
  • Toma vários paramsparâmetros para vários segmentos de URL
  • lança em nulo ou vazio

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

Testes

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
TheGeneral
fonte
@PeterMortensen obrigado pela edição
TheGeneral
Alguns problemas com os testes: // Resultado = test1 / test2 / test3 \ para o quarto e último dos testes de arremessos fornecem ArgumentNullException em vez de ArgumentException
Moriya
3

Minha solução 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
fonte
Esse método auxiliar é muito flexível e funciona bem em muitos casos de uso diferentes. Obrigado!
Shiva
3

Eu criei esta função que facilitará sua 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 para URLs e para caminhos normais.

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
fonte
3

Por que não usar apenas o seguinte.

System.IO.Path.Combine(rootUrl, subPath).Replace(@"\", "/")
Andreas
fonte
Eu estava olhando para a versão PowerShell deste que seria: [System.IO.Path]::Combine("http://MyUrl.com/","/Images/Image.jpg")no entanto isso falhar com um resultado de: /Images/Image.jpg. Remova o /segundo subcaminho e ele funcionará:[System.IO.Path]::Combine("http://MyUrl.com/","Images/Image.jpg")
Underverse 6/18
Boa ideia, mas falha, quando um dos parâmetros é nulo.
Pholpar # 22/18
2

Regras ao combinar URLs com um URI

Para evitar comportamentos estranhos, há uma regra a seguir:

  • O caminho (diretório) deve terminar com '/'. Se o caminho terminar sem '/', a última parte será tratada como um nome de arquivo e será concatenada ao tentar combinar com a próxima parte da URL.
  • Há uma exceção: o endereço URL base (sem informações do diretório) não precisa terminar com '/'
  • a parte do caminho não deve começar com '/'. Se começar com '/', todas as informações relativas existentes da URL string.Emptyserão descartadas ... adicionar um caminho de peça também removerá o diretório relativo da URL!

Se você seguir as regras acima, poderá combinar URLs com o código abaixo. Dependendo da sua situação, você pode adicionar várias partes do 'diretório' ao 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
fonte
2

Se você não deseja adicionar uma dependência de terceiros como Flurl ou criar um método de extensão personalizado, no ASP.NET Core (também disponível no Microsoft.Owin), pode usar o PathStringque se destina ao objetivo de criar URI caminhos. Você pode criar seu URI completo usando uma combinação disso Urie UriBuilder.

Nesse caso, seria:

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

Isso fornece todas as partes constituintes sem a necessidade de especificar os separadores no URL base. Infelizmente, PathStringrequer que /seja anexado a cada string, caso contrário, ele lança um ArgumentException! Mas pelo menos você pode criar seu URI deterministicamente de uma maneira que seja facilmente testável por unidade.

Neo
fonte
2

Então, eu tenho outra abordagem, semelhante a todos que usaram o UriBuilder.

Eu não queria dividir meu BaseUrl (que pode conter uma parte do caminho - por exemplo, http://mybaseurl.com/dev/ ) como javajavajavajavajava .

O seguinte trecho mostra o código + testes.

Cuidado: esta solução coloca em minúsculas o host e anexa uma porta. Se isso não for desejado, pode-se escrever uma representação de string, por exemplo, aproveitando a Uripropriedade 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));
         }
  }

Testado com o .NET Core 2.1 no Windows 10.

Por que isso funciona?

Embora Path.Combineretorne barras invertidas (no Windows pelo menos), o UriBuilder lida com esse caso no Setter de Path.

Retirado de https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/src/System/UriBuilder.cs (lembre-se da chamada para string.Replace)

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

Essa é a melhor abordagem?

Certamente esta solução é bastante auto-descritiva (pelo menos na minha opinião). Mas você está confiando no "recurso" não documentado (pelo menos não encontrei nada com uma pesquisa rápida no google) da API do .NET. Isso pode mudar com uma versão futura, portanto, cubra o método com testes.

Existem testes em https://github.com/dotnet/corefx/blob/master/src/System.Private.Uri/tests/FunctionalTests/UriBuilderTests.cs ( Path_Get_Set) que verificam se a \transformação está correta.

Nota lateral: Também é possível trabalhar UriBuilder.Uridiretamente com a propriedade, se o uri for usado para um System.Urictor.

Tobias Schwarzinger
fonte
Esta é uma abordagem muito confiável. Polegares para cima para o teste de unidade !!
aggsol
2

Para quem procura uma linha única e simplesmente deseja unir partes de um caminho sem criar um novo método ou fazer referência a uma nova biblioteca ou construir um valor de URI e convertê-lo em uma string, então ...

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

É bem básico, mas não vejo o que mais você precisa. Se você tem medo de dobrar '/', basta fazer o .Replace("//", "/")seguinte. Se você tem medo de substituir o '//' dobrado em 'https: //', faça uma junção, substitua o '/' dobrado e junte-se ao URL do site (no entanto, tenho certeza de que a maioria dos navegadores irá automaticamente converta qualquer coisa com 'https:' na frente para ler no formato correto). Seria assim:

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

Há muitas respostas aqui que tratam de tudo o que foi dito acima, mas, no meu caso, eu só precisava dela uma vez em um local e não precisaria depender muito delas. Além disso, é realmente fácil ver o que está acontecendo aqui.

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

DubDub
fonte
1

Usar:

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

Tem o benefício de se comportar exatamente como Path.Combine.

TruthOf42
fonte
1

Aqui está minha abordagem e também a utilizarei para mim:

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
fonte
Isso é aceitável apenas para o seu caso. Há casos que podem danificar seu código. Além disso, você não fez a codificação adequada das partes do caminho. Essa pode ser uma enorme vulnerabilidade quando se trata de ataques de script entre sites.
Believe2014
Eu concordo com seus pontos. O código deve fazer apenas uma combinação simples de duas partes de URL.
Amit Bhagat
1

Usa isto:

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
fonte
Toque agradável com 'WebPath'. :) O código pode ser desnecessariamente denso - é difícil para mim olhar para isso e dizer: sim, isso é perfeito. Isso me faz querer ver testes de unidade. Talvez seja só eu!
precisa
1
x.StartsWith ("/") &&! x.StartsWith ("http") - por que o http verifica? o que você ganha?
Penguat
Você não deseja remover a barra se ela começar com http.
27413 Martin Murphy
@BrianMacKay, não tenho certeza de que dois revestimentos justifiquem um teste de unidade, mas se você quiser, faça um teste. Não é como se eu estivesse aceitando patches ou algo assim, mas fique à vontade para editar a sugestão.
27413 Martin Murphy
1

Eu descobri que o Uriconstrutor vira '\' em '/'. Então você também pode usar Path.Combine, com o Uriconstrutor.

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

Para o que vale a pena, aqui estão alguns métodos de extensão. O primeiro combinará caminhos e o segundo adiciona parâmetros ao 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
fonte
1

Como encontrado em outras respostas, novo Uri()ou TryCreate()pode fazer o tick. No entanto, a base Uri precisa terminar /e o parente NÃO deve começar /; caso contrário, removerá a parte final do URL base

Eu acho que isso é melhor feito como um método de extensão, ou seja,

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

e para usá-lo:

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

Em termos de desempenho, isso consome mais recursos do que o necessário, devido à classe Uri, que faz muita análise e validação; um perfil muito difícil (Debug) realizou um milhão de operações em cerca de 2 segundos. Isso funcionará na maioria dos cenários; no entanto, para ser mais eficiente, é melhor manipular tudo como seqüências de caracteres; isso leva 125 milissegundos para 1 milhão de operações. Ou seja,

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

E se você ainda deseja retornar um URI, leva cerca de 600 milissegundos para 1 milhão de operações.

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

Eu espero que isso ajude.

Mahmoud Hanafy
fonte
1

Eu acho que isso deve lhe dar mais flexibilidade, pois você pode lidar com quantos segmentos de caminho desejar:

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