czwartek, 27 grudnia 2012

Łączenie URL-i jak Path.Combine()

Pracując z aplikacjami webowymi dosyć często zachodzi potrzeba połączenia dwóch stringów będących URL-ami. Można to zrobić w bardzo prosty sposób jak zwykłe łączenie dwóch stringów, przy pomocy plusa. Jednak to rozwiązanie jest nieeleganckie i może być źródłem nieoczekiwanych problemów.

Z drugiej strony w przypadku operowania na stringach będących ścieżkami na dysku możemy skorzystać z metody Path.Combine(), która bezpiecznie łączy dwa ciągi znaków. Dodając lub nie "\" w miejscu ich łączenia.

W przypadku łączenia URL-i nie mamy gotowej metody w frameworku, która działała by w taki sam sposób jak Path.Combine(). Można tu wykorzystać naturalną możliwość łączenia jaką daje konstruktor klasy Uri() jednak jego działanie nie będzie zgodne z oczekiwaniem. Dodatkowym problemem jest to, że nie można podać łączonych URL-i jako stringów.
string urlA = "http://example.org/info";
string urlB = "test.html";

Uri baseUri = new Uri(urlA);
Uri pathUri = new Uri(urlB, UriKind.Relative);
Console.Write(new Uri(baseUri, pathUri).ToString());

Wynikiem działania powyższego kodu będzie adres w postaci: http://example.org/test.html. Nie taki był zamiar, bo zgodnie z oczekiwaniem adres miał wyglądać tak: http://example.org/info/test.html aby zachować zachowanie takie jak Path.Combine().
Gdy lekko zmodyfikujemy łączone adresy:
string urlA = "http://example.org/info/";
string urlB = "test.html";

To otrzymamy właściwy adres zgodnie z oczekiwaniem. A co gdy dołączana część adresu będzie się zaczynać od "/"?
string urlA = "http://example.org/info/";
string urlB = "/test.html";

Otrzymamy znowu adres: http://example.org/info.html, więc wróciliśmy do punktu wyjścia. Kilka spostrzeżeń:
  • Pierwsza część adresu musi zawsze kończyć się znakiem "/"
  • druga część adresu nie może się zaczynać od znaku "/"
Zastosujmy więc poniższe wnioski do początkowego kodu:
string urlA = "http://example.org/info/";
string urlB = "/test.html";

Uri baseUri = new Uri((urlA.EndsWith("/") ? urlA : urlA + '/'));
Uri pathUri = new Uri(urlB.TrimStart('/'), UriKind.Relative);
Console.Write(new Uri(baseUri, pathUri).ToString());
Lub w bardziej przyjaznej formie metody:
public static string UrlCombine(string urlA, string urlB)
{
   // dodajmy / na końcu gdy go nie ma
   Uri baseUri = new Uri((urlA.EndsWith("/") ? urlA : urlA + '/'));
   // zawsze usuwamy znak / z początku
   Uri pathUri = new Uri(urlB.TrimStart('/'), UriKind.Relative); 
   return new Uri(baseUri, pathUri).ToString();
}

Omijając kilka właściwości klasy Uri udało się stworzyć metodę, która może ułatwić i przyspieszyć prozaiczną czynność jak łączenie adresów.