Tento kratší díl našeho seriálu se taky nebude zabývat vývojem komponent, stejně jako minulý díl, ale doufám, že bude užitečný.
V ASP.NET aplikacích potřebujeme často zjišťovat či převádět různé cesty k souborům či webové adresy. Většinu z nich jsme schopni získat ze třídy HttpRequest, ale u některých je to trochu složitější.
Je potřeba rozlišovat pojmy adresa a cesta. Adresa je absolutní či relativní URL, které používáme pro přístup ke stránce (nebo něčemu jinému) pomocí protokolu HTTP. Adresa se zadává do prohlížeče anebo třeba do odkazů ve stránce.
Cestou rozumíme cestu v souborovém systému, která určuje, kde leží daný soubor. Pozor, tady jde o cestu v souborovém systému serveru, ne lokálního počítače, kde si uživatel prohlíží stránku! Cestu používáme, pokud z webové aplikace potřebujeme pracovat se soubory ve vlastní složce (jinam nás bezpečnostní nastavení serveru většinou nepustí, i když jsou výjimky).
Ještě jeden pojem je třeba vysvětlit, a to virtuální adresa. Je to adresa začínající vlnkou, která značí kořenový adresář webové aplikace. Pokud aplikace běží na adrese http://www.vbnet.cz, pak adresa ~/Forum.aspx reprezentuje stránku http://www.vbnet.cz/Forum.aspx. Pokud by aplikace běžela na adrese http://localhost/Test/, pak by to byla stránka http://localhost/Test/Forum.aspx.
Pozor, virtuální adresy jsou věcí ASP.NET, prohlížeče je z logiky věci nezná (neví, kde je kořen aplikace). Nelze je tedy použít všude, jen u elementů ve stránce, které mají runat=”server”.
Přehled funkcí
Vzhledem k tomu, že v cestách a adresách a funkcích, které je poskytují, je značný zmatek, snad pomůže tato přehledová tabulka. Spouští se stránka Default.aspx v adresáři test, tedy ~/test/Default.aspx.
Aplikace běžící na vývojovém serveru ve Visual Studiu na http://localhost:58414/UrlTest/
Request.ApplicationPath | /UrlTest |
Request.AppRelativeCurrentExecutionFilePath | ~/test/Default.aspx |
Request.CurrentExecutionFilePath | /UrlTest/test/Default.aspx |
Request.FilePath | /UrlTest/test/Default.aspx |
Request.Path | /UrlTest/test/Default.aspx |
Request.PhysicalApplicationPath | d:\Temp\UrlTest\ |
Request.PhysicalPath | d:\Temp\UrlTest\test\Default.aspx |
Request.RawUrl | /UrlTest/test/Default.aspx?test=test |
Request.Url | http://localhost:58414/UrlTest/test/Default.aspx?test=test |
HostingEnvironment.ApplicationPhysicalPath | d:\Temp\UrlTest\ |
HostingEnvironment.ApplicationVirtualPath | /UrlTest |
Aplikace běžící na IISce na http://localhost:12345/
Request.ApplicationPath | / |
Request.AppRelativeCurrentExecutionFilePath | ~/test/Default.aspx |
Request.CurrentExecutionFilePath | /test/Default.aspx |
Request.FilePath | /test/Default.aspx |
Request.Path | /test/Default.aspx |
Request.PhysicalApplicationPath | D:\Temp\UrlTest\ |
Request.PhysicalPath | D:\Temp\UrlTest\test\Default.aspx |
Request.RawUrl | /test/Default.aspx?test=test |
Request.Url | http://localhost:12345/test/Default.aspx?test=test |
HostingEnvironment.ApplicationPhysicalPath | D:\Temp\UrlTest\ |
HostingEnvironment.ApplicationVirtualPath | / |
Co co znamená?
Request.ApplicationPath a HostingEnvironment.ApplicationVirtualPath
Tyto dvě vlastnosti vrací to samé – relativní adresu ke kořeni aplikace na serveru. Protože ve druhém případě běží aplikace přímo v kořeni, je cesta jen /. V prvním případě je /UrlTest. Druhá varianta přes HostingEnvironment se používá v případě, že nemáme instanci třídy HttpRequest. Pokud jsme ve stránce, máme ji zpřístupněnou přes vlastnost Request. Pokud jsme jinde, můžeme ji získat přes HttpContext.Current.Request. Někde to ale nejde (například v Global.asax v Application_Start, pokud jsme v integrated módu na IISce). Tam pak použijeme právě HostingEnvironment.
Request.AppRelativeCurrentExecutionFilePath
Virtuální adresa k právě vykonávanému souboru (stránce atd.) v rámci aplikace.
Request.CurrentExecutionFilePath, Request.FilePath a Request.Path
Tyto funkce vrací to samé, liší se jen při použití funkce Server.Transfer (postrčení jiné stránky, než na jakou bylo dotázáno). Request.FilePath a Request.Path vrací relativní adresu ke stránce, na níž byl původní požadavek, Request.CurrentExecutionFilePath vrací adresu stránky, která se skutečně odeslala.
Pokud tedy ve stránce Transfer.aspx zavolám Server.Transfer(“test/Default.aspx”), budou mít Request.FilePath a Request.Path hodnotu /Transfer.aspx, zatímco Request.CurrentExecutionFilePath hodnotu /test/Default.aspx.
Jaký je rozdíl mezi Request.Path a Request.FilePath, to opravdu nevím. V referenčních zdrojácích jsou obě implementované jinak. Víte-li někdo, podělte se v komentářích.
Request.PhysicalApplicationPath a HostingEnvironment.ApplicationPhysicalPath
Vrací cestu v souborovém systému serveru ke kořenovému adresáři aplikace. Pokud chcete zapisovat něco třeba do adresáře App_Data pomocí kódu v aplikaci, tohle jsou ty správné vlastnosti. Anebo použít funkci Server.MapPath("~/App_Data").
Varianta přes HostingEnvironment je opět pro případy, kdy nemáme instanci HttpRequest a HttpContext.Current vrací null.
Request.PhysicalPath
Vrací cestu v souborovém systému ke stránce (skriptu), který se právě provádí.
Request.RawUrl
Adresa (relativní vůči kořeni serveru), na kterou se klient skutečně dotazoval, včetně QueryStringu. Používá se často pro redirect na stránku, kde právě jsme (pro zrušení postbacku například po smazání položky, aby zmáčknutí F5 v prohlížeči nevyvolalo požadavek na smazání znovu) – Response.Redirect(Request.RawUrl).
Request.Url
Vrací celou URL adresu aktuálního požadavku, ne jako string, ale jako System.Uri.
Absolutní URL adresa aplikace
To, co mi vždy v ASP.NET chybělo je HostingEnvironment.AbsoluteApplicationUri nebo něco podobného – absolutní URL adresa aplikace. V prvním případě, když běžíme na lokálním serveru, nechť vrací http://localhost:58414/UrlTest, když na IISce, nechť http://localhost:12345/.
Taková funkce nikde bohužel není (anebo jsem ji nenašel, ale hledal jsem dost dlouho), je tedy třeba si ji napsat. Nedávno jsem se ptal Michala Valáška, jak to dělá, a zjistil jsem, že prakticky stejně jako já.
Já obvykle používám třídu Globals.cs, která obsahuje některé často používané a užitečné funkce, například právě vlastnost ApplicationAbsoluteUri. Ta vypadá zhruba takto:
private static object applicationAbsoluteUriLocker = new object();
private static string applicationAbsoluteUri;
/// <summary>
/// Vrací absolutní URL kořenu aplikace
/// </summary>
public static string ApplicationAbsoluteUri
{
get
{
if (applicationAbsoluteUri == null)
lock (applicationAbsoluteUriLocker)
if (applicationAbsoluteUri == null)
{
// zjistit absolutní URL aplikace
var builder = new UriBuilder(HttpContext.Current.Request.Url);
builder.Fragment = string.Empty;
builder.Query = string.Empty;
builder.Path = HttpContext.Current.Request.ApplicationPath;
applicationAbsoluteUri = builder.ToString();
}
return applicationAbsoluteUri;
}
}
Samotná absolutní URL je uložena v privátní proměnné applicationAbsoluteUri. Při prvním volání vlastnosti ApplicationAbsoluteUri se zjistí, zda-li je proměnná naplněna a pokud ne, naplní se.
Protože se jedná o statickou vlastnost, musí být thread-safe. Zamykám se tedy na vlastní proměnné typu object, která slouží jen tomuto účelu (není dobré zamykat se na něčem jiném, časem na to zapomenete a uděláte si deadlock). Je zde vidět klasická thread-safe inicializace – kontrola přes if, pak zámek, a druhá kontrola. První if je zde proto, abychom při každém použití zbytečně nezamykali, zámky jsou pomalé (ne moc, ale znatelné to je). Pokud by dvě vlákna prošla první podmínkou zároveň nebo těsně po sobě, jedno se uspí na zámku, zatímco druhé pronikne dovnitř a zinicializuje proměnnou. Proto je nutná druhá kontrola, aby v okamžiku, kdy je do kritické sekce vpuštěno druhé vlákno, neprovádělo inicializaci podruhé (ono by to tady nevadilo, ale je dobré se tento vzor naučit – při jiném použití už to vadit může).
Samotné zjištění URL probíhá poměrně snadno – aktuální Uri si dáme do třídy UriBuilder, vymažeme QueryString (?test=blabla atd.) a Fragment (#kotva), i když ten by tam být neměl, je to věc prohlížeče, na server se neodesílá (ale pro jistotu to tam máme). A jako cestu, tedy to, co je za http://server/, dáme relativní adresu ke kořenu aplikace.
A to je celé, uložíme do proměné a vrátíme ji.