Category Archives: ASP.NET

Dynamické přepínání MasterPage

Page má property Page.MasterPageFile.
Hodnotu lze přiřadit výhradně ve fázi Page_PreInit, jinak se vyvolá výjimka.
Hodnotou je cesta k .master souboru.

void Page_PreInit(object sender, EventArgs e)
{
    MasterPageFile = "~/Layout1.master";
}

Implementace dynamických změn není úplně primitivní, protože ve fází PreInit nemáme ještě PostBackData, ani neproběhly žádné RaisedEventy.
V podstatě musíme použít nějaké „nezávislé“ uložiště pro použitý Master file (např. Profile nebo Session) a řešit kolizi, že MasterPageFile je potřeba nastavit u content stránky, kdežto přepínač layoutů může být už v samotném master file.
Jde to udělat nějak takto:

Content.aspx:

void Page_PreInit(object sender, EventArgs e)
{
    MasterPageFile = Profile.MasterPageFile; // Session["MasterPageFile"];
}

Layout.master:

void MasterDDL_Changed(object sender, EventArgs e)
{
   Profile.MasterPageFile = MasterDDL.SelectedValue;
   Response.Redirect(Request.Path); // !!!! nebo ReturnUrl
}

Response.Redirect je zde potřeba, protože ke změně došlo až po fázi Page_PreInit a my potřebujeme znovunačtením stránky projít přes Page_PreInit. MasterPage si můžeme ukládat i do cookie, každopádně mechanizmus přepínání je podobný jako u lokalizace.

Pokud bychom změnu chtěli udělat jediným roundtripem, museli bychom v PreInit sami parsovat data z Forms nebo QueryStringu. Nejjednodušší je přepínání nasměrovat na samostatný soubor s ReturnUrl, jako se to dělá u lokalizace nebo loginu.

SmptMail.Send: The ‚SendUsing‘ configuration value is invalid.

Ač dokumentace tvrdí, že se standardně použije lokální SMTP server, přesto někdy problém odstraní nastavení:

SmtpMail.Server = "localhost";

…každopádně v .NET 2.0 je lepší použít mailování z namespace System.Net.

Dynamicky přidávané controly musí mít nastaveno ID, jinak jim blbne postback

Pokud do control-tree přidáváme dynamicky nějaké controly, které mají obsluhu postbacku (data nebo event), pak pokud těmto controlům explicitně nanastavíme nějaké ID, může se nám snadno stát, že postback nebude korektně vyhodnocen, např.:

  • Button, LinkButton, ImageButton nebudou emitovat události Click, Command, …,
  • inputové controly (TextBox, DropDownList, …) zapomenou přes roundtrip data,

…v zábavnějším případě se nám může stát, že postback není korektní jen u některých dynamický controlů a u některých se vyhodnotí správně (třeba pokud přidáváme řádky objednávky, tak nám data zapomíná jen poslední řádek).

Nevím, jestli je to bug, ale dělalo to už v .NET 1.1 a dělá to i v .NET 2.0.

Každopádně nastavením ID u dynamicky přidávaných controlů se těchto potíží zbavíme!

private void CreateControlsHiearchy()
{
   LinkButton lb = new LinkButton();
   lb.ID = "MyLB"; // <--- bez toho není jisté, že se nám bude Click korektně volat !!!
   lb.Click += ...
   ...
   Controls.Add(lb);
}

Control pro zadávání předem neznámého počtu položek

Ukažme si základní schema jednoduchého controlu, který umožní uživateli zadat data předem neurčeného počtu položek – umožní tedy uživateli dynamické přidávání dalších položek – např. řádky objednávky, několik telefonních čísel, několik e-mail e-mailů – v našem případě několik zásilek k přepravě.

 

Netvrdím, že níže uvedený postup je jediný možný a uvítám jakoukoliv diskuzi pod článkem, berte to spíše jako nasměrování pro další snažení…

Neřeším také, jak na control navázat již existující data (možná v příštím článku), a i vytahování dat z controlu není úplně user-friendly. Nicméně cílem je demonstrovat základní schéma dynamického počtu položek a vylepšení kolem už si každý jistě umí udělat.

Základní schema by se dalo shrnout:

  1. Vytvoříme si control představující jednu položku – není to sice nezbytně nutné, ale usnadní to další manipulaci – control samozřejmě může už existovat, pokud se ptáme jen na jména milenek, může nám stačit už TextBox – nicméně většinou budeme potřebovat control, který bude obsahovat nekolik prvků, validátory, DropDownListy naplněné daty, atp. V našem konkrétním případě jsem si teda připravil UserControl Zasilka.ascx, který má za předka třídu Zasilka (code-behind).
  2. Vytvoříme CompositeControl, který bude v CreateChildControls dynamicky přidávat tolik položek, kolik je právě potřeba. Aby nám fungoval postback a viewstate, musí být control-tree vystavěn při každém roundtripu znovu, a to právě ve fázi Init. V CompositeControl je připraveno schéma pomocí metody CreateChildControls(), která je volána právě už ve fázi Init. Zásadním problémem zde je, jak ve fázi Init zjistit, kolik položek máme zrovna vygenerovat, když ještě nemáme načtena post-back data, ani ViewState.
  3. Aktuální počet položek si musíme ukládat tak, abychom ho znali už ve fázi Init, například do hidden-inputu. Lze využít i Session, či jiná uložiště, vlastní hidden-input se však zde nabízí jako ideální.
  4. Přidání další položky realizujeme v event-handleru pomocí Controls.AddAt(), odebírání položky pomocí Controls.RemoveAt(). Klikne-li uživatel na tlačítko „přidat další položku“, dozvíme se o tom v event-handleru až v poměrně pokročilé fázi zpracování postbacku, když už je control-tree vytvořen (jinak by ani event nemohl být korektně obsloužen). V této fázi jsou existující položky i naplněny daty a nebylo by tedy vhodné znovu rebuildovat control-tree, protože bychom tato data ztratili, popř. museli znovu dotahovat. Protože však control-tree díky jeho pevné stavbě známe, nic nám nebrání přidat další položku do existujícího control-tree, popř. položku odebrat.
  5. Data z controlu vytahujeme například přímým přístupem do Controls, jelikož strukturu položek v Controls známe.

Kód takového controlu s dynamickým počtem položky by tedy mohl vypadat nějak následovně:

public class Zasilky : CompositeControl
 {
  // Počet zásilek si mezi postbacky posíláme v input-hidden,
  // z něj ho načítáme přímo pomocí Form, ukládáme až těsně
  // před renderem OnPreRender(), abychom měli poslední stav
  // po případném přidání/odebrání.
  public int PocetZasilek
  {
   get
   {
    if (_pocetZasilek == null)
    {
     _pocetZasilek = 1;
     object tmp = Page.Request.Form[this.ClientID + &quot;_PocetZasilek&quot;];
     if (tmp != null)
     {
      _pocetZasilek = Convert.ToInt32(tmp);
     }
    }
    return (int)_pocetZasilek;
   }
   set
   {
    _pocetZasilek = value;
   }
  }
  private int? _pocetZasilek;
  
  // abychom mohli buttony referencovat (chceme je skrývat),
  // uložíme si odkaz na ně do private fieldu
  private LinkButton pridejZasilkuLB;
  private LinkButton odeberZasilkuLB;
  
  // uloží nám počet položek do input-hidden
  protected override void OnPreRender(EventArgs e)
  {
   Page.ClientScript.RegisterHiddenField(this.ClientID + &quot;_PocetZasilek&quot;, _pocetZasilek.ToString());
   
   base.OnPreRender(e);
  }
  
  // klasické schéma CompositeControlu
  protected override void CreateChildControls()
  {
   Controls.Clear();
   CreateControlHiearchy();
   ClearChildViewState();
  }
  
  // budujeme control-tree
  private void CreateControlHiearchy()
  {
   // položky - jsou naschvál na začátku, což nám usnadňuje jejich snadné referencování pomocí Controls[i],
   // jinak bychom museli přístup k nim mít složitější
   for (int i = 0; i &lt; PocetZasilek; i++)
   {
    CreateZasilkaControl(Controls.Count);
   }
  
   Literal lit1 = new Literal();
   lit1.Text = &quot;&lt;tr&gt;&lt;td colspan=\&quot;3\&quot; class=\&quot;zindent\&quot;&gt;&quot;;
   Controls.Add(lit1);
  
   // tlačítko pro přidání položky
   pridejZasilkuLB = new LinkButton();
   pridejZasilkuLB.ID = &quot;PridejZasilkuLB&quot;; // &lt;-- nutno nastavit ID, jinak nám můžou blbnout postbacky
   pridejZasilkuLB.Text = (string)HttpContext.GetGlobalResourceObject(&quot;Zasilky&quot;, &quot;PridejZasilku&quot;);
   pridejZasilkuLB.CssClass = &quot;arrow&quot;;
   pridejZasilkuLB.Click += new EventHandler(pridejZasilkuLB_Click);
   pridejZasilkuLB.CausesValidation = false;
   Controls.Add(pridejZasilkuLB);
  
   Literal lit3 = new Literal();
   lit3.Text = &quot;&lt;br/&gt;\n&quot;;
   Controls.Add(lit3);
  
   // tlačítko pro odebrání položky
   odeberZasilkuLB = new LinkButton();
   odeberZasilkuLB.ID = &quot;OdeberZasilkuLB&quot;; // &lt;-- nutné nastavit ID, jinak nám můžou blbnout postbacky
   odeberZasilkuLB.Text = (string)HttpContext.GetGlobalResourceObject(&quot;Zasilky&quot;, &quot;OdeberPosledniZasilku&quot;);
   odeberZasilkuLB.CssClass = &quot;arrow&quot;;
   odeberZasilkuLB.Click += new EventHandler(odeberZasilkuLB_Click);
   odeberZasilkuLB.CausesValidation = false;
   odeberZasilkuLB.Visible = (PocetZasilek &gt; 1);
   Controls.Add(odeberZasilkuLB);
  
   Literal lit2 = new Literal();
   lit2.Text = &quot;&lt;/td&gt;&lt;/tr&gt;\n&quot;;
   Controls.Add(lit2);
  }
  
  // vytvoření jedné položky v control-tree
  private void CreateZasilkaControl(int index)
  {
   Zasilka zasilkaControl = (Zasilka)Page.LoadControl(&quot;~/Controls/Zasilka.ascx&quot;);
   zasilkaControl.ID = &quot;ZasilkaUC_&quot; + index.ToString(); // &lt;-- nutné nastavit ID kvůli korektním postback
   Controls.AddAt(index, zasilkaControl);
  }
  
  // obsluha události - přidání položky
  private void pridejZasilkuLB_Click(object sender, EventArgs e)
  {
   PocetZasilek++;
   CreateZasilkaControl(PocetZasilek - 1); // přidáme za poslední položku
   odeberZasilkuLB.Visible = true;
  }
  
  private void odeberZasilkuLB_Click(object sender, EventArgs e)
  {
   if (PocetZasilek &gt; 1)
   {
    PocetZasilek--;
    Controls.RemoveAt(PocetZasilek); // odebereme poslední položku
    if (PocetZasilek == 1)
    {
     odeberZasilkuLB.Visible = false;
    }
   }
  }
 }

Vytahování dat z controlu by pak v nejjednoduším případě mohlo vypadat nějak takto:

for (int i=0; i &lt; Zasilky.PocetZasilek; i++)
{
   Zasilka zasilka = (Zasilka)Zasilky.Controls[i];
   uloziste[i] = zasilka.Hmotnost;
}
  
// kdyby byl položkou jen TextBox
for (int i=0; i &lt; Zasilky.PocetZasilek; i++)
{
   TextBox polozka = (TextBox)MyControl.Controls[i];
   uloziste[i] = polozka.Text;
}

Attachment: screenshot.gif

App_offline.htm – explicitní odstavení webové aplikace

ASP.NET 2.0 má v sobě jednu novou málo známou pomůcku – možnost dočasného odstavení webové aplikace umístěním souboru App_offline.htm do rootu webové aplikace.

Pokud do rootu aplikace umístíme soubor App_offline.htm, webová aplikace se zastaví a dokonce se zruší její aplikační doména (AppDomain), takže se odpojí všechny otevřená spojení, databázové soubory, zruší user-instance SQL, atp.

Případný request dostane v odpovědi obsah App_offline.htm souboru. Můžeme si tak připravit nějaký rozumný soubor a pouhým přejmenováním na App_offline.htm web shazovat.

Funkčnost je zamýšlena pro krátkodobé odstavení webové aplikace za účelem jejího update a využívá jí například i Visual Studio při „Publish Web Site“.

Jak umoudřit eventy po ENTER ve formuláři

Pokud na straně klienta odešleme formulář klávesou ENTER, pak máme-li na stránce jediný submit-prvek (tlačítko) a k tomu jediný input-prvek (TextBox), pak díky chování mnohých prohlížečů (včetně Internet Exploreru), nedojde k vyvolání serverové události Click submit-prvku, nýbrž proběhne jen hluchý postback.

Ošálit to lze přidáním dalšího skrytého TextBoxu:

<asp:TextBox ID="EmailTB" Runat="server"/>
<asp:TextBox ID="dummy" Style="display:none;" Runat="server"/>
<asp:LinkButton ID="SubscribeLB" Text="Přihlásit" Runat="server"/>

Souborový přístup ke složkám nadřazeným/sousedním webové aplikaci

Dejme tomu, že jsme v hostovaném prostředí a víme, že je na disku zhruba následující adresářová struktura:

<nevíme>/Zakaznik/
<nevíme>/Zakaznik/wwwroot/
<nevíme>/Zakaznik/data/

…a potřebujeme se z webové aplikace dostat na soubory ve složce ../data/.

Na problém narazíme, pokud bychom použili

 Server.MapPath("../data/cosi.xyz");
Server.MapPath("/../data/cosi.xyz");
Server.MapPath("~/../data/cosi.xyz");
 

…dostaneme výjimku HttpException: Cannot use a leading .. to exit above the top directory.

Pomoc je snadná, pokud máme na cílové místo opravdu přístupová práva, pak stačí použít:

 Path.Combine(Server.MapPath("/"), "../data/cosi.xyz")

…a jsme tam.

Lokalizace snadno a rychle – explicitní lokalizace

Předesílám, že se v tomto článku budu zabývat výhradně explicitní lokalizací webových projektů, tedy přímým odkazováním na resources pomocí <%$ Resources: … %>, popř. metod GetLocalResourceObject() nebo GetGlobalResourceObject(). Osobně mám tuto metodu radši, protože mám přesně pod explicitní kontrolou každý bajt, který chci lokalizovat.

Co všechno je tedy pro úspěšnou implementaci lokalizace udělat?

  1. Vytvořit resources – .resx soubory s lokalizovanými texty, popř. i jinými objekty.
  2. Přidat do stránek/kódu odkazy na resources tam, kde chceme lokalizaci.
  3. Vytvoření lokalizovaných verzí .resx souborů.
  4. Zajistit nastavení a případně i přepínání CurrentUICulture, popř. i CurrentCulture.

Vytvoření primárních resources – .resx souborů

Předpokládám běžnou práci ve Visual Studiu nebo Web Developer Express, nebudu se tedy zabývat takovými věcmi jako je resgen. VS/WDE přímo podporují přehledné vytváření a editaci .resx souborů.

Důležité je, že rozlišujeme dva typy resources:

  1. globální resources
    • data v nich jsou přístupná ze všech míst webové aplikace,
    • jsou umístěny ve složce ~/App_GlobalResources
    • pojmenovávají se MojeJmeno.resx, např. ~/App_GlobalResources/Glossary.resx
    • může jich být libovolné množství, nicméně je rozumné používat jen několik logických celků, např. Navigation.resx, Glossary.resx, atp.
  2. lokální resources
    1. data v nich jsou přímo přístupná jen ze stránky/controlu/…, které se týkají,
    2. umísťují se do složky App_LocalResources, která je podsložkou sloužky, kde je stránka/control,
    3. pojmenovávají se MojeStranka.aspx.resx, popř. MujControl.ascx.resx, atp.; například pro stránku ~/admin/stranka.aspx bude lokální resource file ~/admin/App_LocalResources/stranka.aspx.resx

Resource-file vytvoříme snadno, ve VS/WDE prostě vytvoříme příslušnou složku a do ní přes Add přidáme položku typu Resource. Resource soubor .resx je ve skutečnosti XML souborem, který je přibuildován do assembly, pozadí však nechme stranou.

Po otevření .resx souboru se nám standardně zobrazí grid pro editaci/přidávání objektů typu string. Resource nemusí být jen typu string, ale například i obrázky, ikony, zvuky, soubory nebo obecné objekty, nicméně pro běžnou lokalizaci si naprosto vystačíme s typem String, protože ve webových aplikací i u obrázků a ostatních objektů obvykle „lokalizujeme“ jen odkaz na příslušný soubor na disku (ImageUrl = „vlajka_cz.gif“) spíše než samotné soubory.

V praxi .resx záznamy v .resx souborech vznikají současně s prvním odkazem na ně, přistupme tedy k použití ve stránce/kódu.

Použití resources ve stránce/kódu

Explicitní použití resource ve stránce je primitivní, v tagu controlu, všude, kde lze přiřadit string, stačí zapsat odkaz

  • <%$ Resources: ResourceName %> pro lokální resources
  • <%$ Resources: FileNameBezPripony, ResourceName %> pro globální resources
  • Například tedy
    &lt;asp:Localize Text="&lt;%$ Resources: UvodniText %&gt;" ... runat="server" /&gt;
    &lt;asp:Label Text="&lt;%$ Resources: Glossary, Telefon %&gt;" ... runat="server" /&gt;
    &lt;asp:Image ImageUrl="&lt;%$ Resources: Images, Vlajka %&gt;" ... runat="server" /&gt;
    

    Pozor, že nejde použít <%$ … %> mimo serverové tagy, pro takový účel slouží právě nový control Localize, který není ničím jiným než Literalem s lepší design-time podporou.

    Pro použití resources v kódu je několik možností

    1. Globální resources se zrcadlí do strong-typed properties ve třídách namespace Resources. Například Resources.Glossary.Telefon nebo Resources.Images.Vlajka. Tento wrapper generuje ASP.NET v rámci web-site a je tak přístupný pouze v rámci web-site a nelze na něj odkazovat například z jiných assembly.
    2. Pro obecný přístup ke globálním a lokálním resources slouží metody třídy HttpContext
    public static object GetGlobalResourceObject(string classKey, string resourceKey);
    public static object GetGlobalResourceObject(string classKey, string resourceKey, CultureInfo culture);
    public static object GetLocalResourceObject(string virtualPath, string resourceKey);
    public static object GetLocalResourceObject(string virtualPath, string resourceKey, CultureInfo culture);
    // typicky třeba maily
    mail.Subject = (string)HttpContext.GetGlobalResourceObject("MailTemplates", "MyMailSubject");
    // nebo ve stránce
    MessageLb.Text = (string)GetLocalResourceObject("Neuspech");
    

    Metody jsou mimo třídy HttpContext přístupné i v třídě TemplateControl a jejích potomcích, tedy např. i Page.
    Pro local resources bohužel ASP.NET žádný wrapper negeneruje a z kódu na ně přistupujeme  rovnou přes GetLocalResourceObject().

    Pro přístup k resources můžeme použít samozřejmě i standardní techniky přes ResourceManager atp., ale ve webových aplikacích to není běžné.

    Co jsme dosud udělali nám samo o sobě už bude fungovat a zobrazovat, nicméně stále jsme jen u jednoho primárního jazyka. Pro přidání dalších jazyků a případně i možnost jejich přepínání musíme provést ještě dva kroky.

    Vytvoření resources (.resx souborů) pro další jazyky

    Vytvoření resources pro další jazyky je velmi primitivní záležitostí. Stačí vzít .resx soubor primárního jazyka a vytvořit jeho kopii se jménem Soubor.jazyk.resx, například tedy ze souboru ~/App_GlobalResources/Glossary.resx vytvoříme kopii ~/App_GlobalResources/Glossary.en.resx (pro angličtinu), nebo ze souboru ~/App_LocalResources/Login.aspx.resx vytvoříme kopii ~/App_LocalResources/Login.aspx.de.resx (pro němčinu).

    …a nyní nezbývá než hodnoty v novém souboru přeložit. Pokud navíc nainstalujeme překladateli WDE a naučíme ho editovat přímo .resx soubory, krása. Existují dokonce mnohé šikovné utilitky pro práci s .resx soubory, aby nebylo nutné instalovat něco tak velkého jako WDE:

    Volba jazyka a přepínání jazyků

    Jakou jazykovou verzi odešle ASP.NET na klienta se řídí hodnotou Thread.CurrentThread.CurrentUICulture, zároveň se doporučuje volit i Thread.CurrentThread.CurrentCulture, protože tím se řídí formát čísel, času, dat, měny, atp.

    Automatická volba dle požadavku prohlížeče

    První, co můžeme chtít, je, aby ASP.NET použilo jazykový požadavek prohlížeče, který ho posílá v HTTP requestu. Česká prostředí tedy rovnou uvidí české stránky, anglická anglické, atp. a nic nemusíme ani přepínat. Prohlížeče mají jazyk přednastaven od instalace a například v Internet Exploreru je možné ho měnit přes Nástroje ~ Možnosti Internetu ~ Obecné ~ Jazyky.

    Jediné, co pro implementaci tohoto postupu potřebujeme udělat, je ve web.config nastavit hodnoty elementu <globalization>, atributy uiCulture, popř. culture:

    &lt;globalization
       requestEncoding="utf-8"
       responseEncoding="utf-8"
       culture="auto:cs-CZ"
       uiCulture="auto:cs-CZ"
    /&gt;
    

    Volba auto říká, že se má použít požadavek prohlížeče, za dvojtečku lze volitelně umístit primární nastavení, které se má použít, pokud není požadavek prohlížeče realizovatelný, resp. pokud prohlížeč požadavek neudal. Pro úplnost uvádím i volby encoding, ty však nejsou předmětem tohoto článku.

    Explicitní volba jazyka uživatelem

    Dále můžeme chtít, aby si uživatel sám mohl přepnou jazyk, nezávisle na požadavku prohlížeče. Je jasné, že budeme muset přepnout Thread.CurrentThread.CurrentUICulture popř. i CurrentCulture. Zvolený jazyk si budeme ukládat třeba do cookie (použitelná je i session) a je potřeba ho přepnout pro každý request, a to co nejdříve. Buď můžeme udělat override prázdné virtuální metody Page.InitializeCulture(), pokud máme připravenou bázovou třídu pro všechny stránky, anebo změnu provádět rovnou v události Application_BeginRequest, v Global.asax.cs:

    private void Application_BeginRequest()
    {
       HttpCookie cookie = Request.Cookies["Culture"];
       if (cookie != null)
       {
          CultureInfo culture = new CultureInfo(cookie.Value);
          Thread.CurrentThread.CurrentCulture = culture;
          Thread.CurrentThread.CurrentUICulture = culture;
       }
    }
    

    a pro vlastní přepínání si uděláme třeba stránku Language.aspx:

    protected override void OnInit(EventArgs e)
    {
       string culture = Request.QueryString["culture"];
       if ((culture != null) &amp;&amp; (culture.Length &gt; 0))
       {
          HttpCookie cookie = new HttpCookie("Culture");
          cookie.Value = culture;
          Response.Cookies.Add(cookie);
       }
       string returnUrl = Request.QueryString["returnUrl"];
       if ((returnUrl != null) &amp;&amp; (returnUrl.Length &gt; 0))
       {
          Response.Redirect(returnUrl);
       }
       else
       {
          Response.Redirect("~/");
       }
       base.OnInit(e);
    }
    

    odkaz pro přepnutí stránky, pak může být třeba

    &lt;a href="language.aspx?culture=en-US"&gt;en&lt;/a&gt;
    

    nebo s ReturnUrl stejně jako login-page.

    Záznam z cvičné přednášky na toto téma

    V příloze najdete PowerPoint slides: Lokalizace webových aplikací.ppt

Jak přistupovat na HttpContext.Current v novém threadu

Když si v ASP.NET spustíme nový thread (vlákno), tak tento sice získá některé vlastnosti z původního threadu (např. culture), ale nemá přístup k HttpContext.Current, resp. je v něm null.

V každém případě musíme vědět, co děláme, protože druhé vlákno například nemůže moc manipulovat s Response, neboť není vůbec jisté, do jakého stavu Response uvedl thread první, např. už totiž mohl Response odeslat na klienta.

Společné uložiště threadů – statická property

První možností je připravit hodnoty, které bude nový thread potřebovat, do uložiště, které je přístupné oboum threadům, klasicky třeba statické property nějaké třídy.

public static string RootUrl
{
   get
   {
      if (rootUrl == null)
      {
         if (HttpContext.Current != null)
         {
            rootUrl = "http://" + HttpContext.Current.Request.Url.Host + HttpContext.Current.Request.ApplicationPath;
         }
         else
         {
            rootUrl = "http://nejaky/default/url";
         }
      }
      return rootUrl;
   }
}
private static string rootUrl;

Výše uvedený příklad statické property zahrnuje i určitou logiku. Rozhodující je však uložení výsledku prvního volání do statické privátní proměnné rootUrl. Pokud si tedy před spuštěním nového threadu zaručíme alespoň jedno čtení dané property, ta si inicializuje svojí hodnotu v prvním threadu a v druhém threadu už dá rovnou výsledek připravený v rootUrl (nebude HttpContext.Current potřebovat).

Teoreticky lze do statické property typu HttpContext cachovat celý HttpContext.Current, nepovažuji to však za příliš vhodné.

Předání contextu v instanci třídy zapouzdřující thread

Druhou možností, která je vhodnější, pokud už potřebujeme předat do druhého threadu opravdu celý context, je zapouzdření druhého threadu do samostatné třídy:

public class MyClass 
{ 
   private HttpContext _context; 
   public MyClass(HttpContext context)
   {
      _context = context;
   } 
   public void Start() 
   { 
      ThreadStart start = new ThreadStart(DoWork); 
      Thread.Start(start); 
   } 
   private void DoWork() 
   { 
      // tady můžem něco dělat z contextem,
      // ale pozor, že request už může být odeslán na klienta
   } 
} 

private void Page_Load(object sender, EventArgs e) 
{ 
   MyClass myClass = new MyClass(HttpContext.Current); 
   myClass.Start(); 
}
HttpContext.Current.Cache

Pokud potřebujeme jen HttpContext.Current.Cache, tak je vše výše uvedené nesmysl, protože na cache máme přístupovat přes HttpRuntime.Cache, a to funguje i v novým threadu…

Vyčištění webové Cache (application cache)

Někdy se nám může hodit kompletní vyčištění webové application cache (datové cache přístupné přes HttpRuntime.Cache, Control.Cache, HttpContext.Current.Cache, …), například po změně cachovaných hodnot „read-only“ číselníků.

.NET Framework pro takovou operaci žádnou přímou pomůcku nemá, nebo o ní alespoň nevím. Jde to však obejít poměrně snadno. Třída Cache totíž implementuje rozhraní IEnumerable a metoda GetEnumerator() vrací IDictionaryEnumerator. Stačí tedy po jedné vyházet všechny položky:

 foreach (DictionaryEntry de in Cache)
{
   Cache.Remove(de.Key.ToString());
}
 

Implementováno v Havit.Web.HttpServerUtilityExt.ClearCache().

Viz též