Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

Software Architect, Founder at HAVIT, Microsoft MVP - ASP.NET/IIS

Co by měl každy vědět o ViewState

1. ViewState není odpovědný za zachování vlastních hodnot controlů mezi postbacky

ViewState není potřeba pro zachování hodnot (Value) TextBoxů, CheckBoxů, DropDownListů a jiných Web controlů mezi postbacky. Tyto hodnoty jsou uloženy standardně ve formulářových postback datech (POST/GET) a ASP.NET je nastaví v metodě LoadPostData() pro všechny prvky, které implementují rozhraní IPostBackDataHandler. Na to, aby nám mezi roundtripy zůstal text v TextBoxu tedy nepotřebujeme ViewState!!!

2. Na co tedy ViewState?

ViewState je vlastnost každého controlu zděděná od System.Web.UI.Control (má ji tedy i Page) a jeho základní funkčnost je založena ná následující implementaci vlastností:

public string NavigateUrl
{
   get
   {
      string text = (string) ViewState["NavigateUrl"];
      if (text != null)
         return text;
      else
         return string.Empty;
   }
   set
   {
      ViewState["NavigateUrl"] = value;
   }
}

Hodnoty vlastností (property) se tedy ukládají do a čtou z ViewState, veškeré jejich změny se promítají do ViewState.

3. ViewState je typu StateBag

ViewState je typu System.Web.UI.StateBag, která implementuje mj. IDictionary (slouží k ukládání párů klíč-hodnota) a interně používá HybridDictionary.
Důležitou metodou StateBagu je SaveViewState(), která odpovídá za uložení ViewState.
Celý fígl je v tom, že metoda SaveViewState() uloží jenom ty vlastnosti, které se změnily po zavolání metody TrackViewState().

4. Co se tedy ukládá při uložení ViewState?

Funkčnost ViewState je úzce spojena s life-cyclem stránky a okamžikem volání metody TrackViewState(). Ta je volána na konci události Init každého controlu, a tedy i stránky (pro zjištění, zda-li již byla volána, lze použít property IsTrackingViewState).
Změny properties provedené před koncem události Init každého controlu se tedy s ViewState neukládají, veškeré další změny až do volání metodySaveViewState() (v události SaveViewState) ano.

5. Jak tedy stránka/control s ViewState funguje?

Vezměme si krátký příklad:

   private void btnSubmit_Click(object sender, EventArgs e)
   {
      lblMessage.Text = "Goodbye, Everyone!";
   }

Co se stane při první návštěvě stránky:

  1. „Instantiation stage“: Nastaví se lblMessage.Text=“Hello, World!“
  2. „Load ViewState stage“: nic se nestane, není postback
  3. „Save ViewState stage“: nic se nestane, nejsou změny ViewState
  4. „Render Stage“: Label je renderován s „Hello, World!“

Co se stane při kliku na Change Message tlačítko:

  1. „Instantiation stage“: Nastaví se lblMessage.Text=“Hello, World!“
  2. „Load ViewState stage“: nic se nestane, ViewState stránky je prázdný
  3. „Raise Postback Event“: btnSubmit_Click nastaví
  4. lbl.Message=“Goodbye, Everyone!“
  5. „Save ViewState stage“: property Text od Labelu je uložena do ViewState, protože se změnila (po volání TrackViewState())
  6. „Render Stage“: Label je renderován s „Goodbye, everyone!“

Co se stane při kliku na Empty postback tlačítko:

  1. „Instantiation stage“: Nastaví se lblMessage.Text=“Hello, World!“
  2. „Load ViewState stage“: nastaví se lblMessage.Text=“Goodbyw, Everyone!“ z ViewState
  3. „Save ViewState stage“: property Text od Labelu je uložena do ViewState, protože se změnila (po volání TrackViewState())
    „Render Stage“: Label je renderován s „Goodbye, everyone!“

Shrnutí & spol.

  1. Do ViewState se ukládají všechny změny v properties controlů provedené po ukončení události Init.
  2. Protože ViewState ukládá pouze vlastnosti controlů a ne controly samotné, musíme dynamicky přidávané controly přidávat při každém postbacku stránky znovu a znovu – nejlépe během události Init (uděláme-li to však i později, metoda .Add() zajistí nahrání ViewState do přidávaných controlů).
  3. U editovatelných DataGridů je vypnutí ViewState docela dřina.
  4. ViewState se ukládá rekurzivně včetně ViewState child-controlů a to v serializované podobě pomocí LOSFormateru.
  5. ViewState lze ukládat i na serveru na disk nebo do databáze pomocí překrytí metod SavePageStateToPersistenceMedium() aLoadViewStateFromPersistenceMedium(), které standardně právě používají hidden-field ___VIEWSTATE. V některém z dalších článků si ukážeme ukládání do Session.
  6. Další možností na zmenšení ViewState je jeho komprese a dekomprese.
  7. ViewState je chráněn před změnami pomocí machine authentication check (MAC), který však pouze kontroluje, je-li ViewState od stejné verze stránky.
  8. ViewState lze i šifrovat, rozhodně se však nedoporučuje pro ukládání citlivých informací.

BUG: MasterPage zobrazuje default hodnotu ContentPlaceHolderu a ignoruje Content

Narazil jsem na zajímavý bug v ASP.NET 2.0 (2.0.50727)…

Pokud v ContentPlaceHolderu definujeme default obsah a použijeme v něm ebedded code block (starý vnořený ASP-style blok kódu <% %>, ale i <%= %>), pak ASP.NET ignoruje Content definovaný v konkrétních stránkách a stále zobrazuje pouze default obsah z MasterPage.

...
<asp:ContentPlaceHolder ID="ContentPlaceHolder1" runat="server">
   <% %>   <-- už tohle vadí
   <%= "Tohle taky vadí" %>
   <%= ResolveUrl("~/takhle-na-to-asi-narazime/") %>
</asp:ContentPlaceHolder>
...

Zajímalo mě, kde je pravděpodobná příčina problému, takže jsem se díval, co z toho ASP.NET stvoří a jak se to liší od funkční podoby. Zásadní rozdíl je už v metodě __BuildControl, kde narozdíl od korektní podoby (ContentPlaceHolder1 s jedním Labelem):

private ContentPlaceHolder __BuildControlContentPlaceHolder1()
{
      ContentPlaceHolder holder1 = new ContentPlaceHolder();
      this.ContentPlaceHolder1 = holder1;
      holder1.ID = "ContentPlaceHolder1";
      if (base.ContentTemplates != null)
      {
            this.__Template_ContentPlaceHolder1 = (ITemplate) base.ContentTemplates["ContentPlaceHolder1"];
      }
      if (this.__Template_ContentPlaceHolder1 != null)
      {
            this.__Template_ContentPlaceHolder1.InstantiateIn(holder1);
            return holder1;
      }
      IParserAccessor accessor1 = holder1;
      accessor1.AddParsedSubObject(new LiteralControl("\r\n\t\t\t"));
      Label label1 = this.__BuildControlTest();
      accessor1.AddParsedSubObject(label1);
      accessor1.AddParsedSubObject(new LiteralControl("\r\n        "));
      return holder1;
}

…chybí řádek return holder1;

private ContentPlaceHolder __BuildControlContentPlaceHolder1()
{
      ContentPlaceHolder holder1 = new ContentPlaceHolder();
      this.ContentPlaceHolder1 = holder1;
      holder1.ID = "ContentPlaceHolder1";
      if (base.ContentTemplates != null)
      {
            this.__Template_ContentPlaceHolder1 = (ITemplate) base.ContentTemplates["ContentPlaceHolder1"];
      }
      if (this.__Template_ContentPlaceHolder1 != null)
      {
            this.__Template_ContentPlaceHolder1.InstantiateIn(holder1);
            // return holder1;  <-- pravděpodobně chybí
      }
      holder1.SetRenderMethodDelegate(new RenderMethod(this.__RenderContentPlaceHolder1));
      return holder1;
}
 
private void __RenderContentPlaceHolder1(HtmlTextWriter __w, Control parameterContainer)
{
      __w.Write("\r\n\t\t\t");
}

Jinak bug je již reportován v Microsoft Connect (Feedback center), můžete se připojit k hlasování…

V odesílaných mailech chybí háčky a čárky, někde samy mizí

Pozoruhodná situace může nastat, pokud explicitně nenastavíme encoding mailů odesílaných z ASP.NET aplikací (resp. obecně .NET aplikací).

Může se nám totiž stát, že aplikace, která na jednom serveru korektně odesílá maily, v pohodě s češtinou, po přesunu na server jiný (jinak nastavený), najednou posílá maily „napůl české“. Ono „napůl“ je na tom to nejzáludnější, zprávy totiž nechodí ve stylu rozsypaný čaj, jak to známe ze špatně nastaveného encodingu, nýbrž jsou ve zprávě odebrány háčky a čárky od většiny českých znaků (zůstává třeba á, í, é). Člověka tak hned nenapadne, v čem je problém a bádá někde mimo aplikaci.

…v mém konkrétním případě druhý server díky svému nastavení posílal zprávy v encodingu iso-8859-1 a sám (předpokládám CDO, ale nebádal jsem nad tím) si je upravoval na nejlépe čitelné.

Zásadně tedy vždy explicitně určovat encoding zpráv!!!

// v případě System.Web.Mail i System.Net.Mail
using (MailMessage mail = new MailMessage())
{
   mail.BodyEncoding = Encoding.GetEncoding("iso-8859-2");
   // System.Net.Mail.MailMessage má i SubjectEncoding
   ...
}

Tisk dlouhých tabulek – záhlaví a zápatí na každé stránce

Poměrně neznámým fíglem lze v některých browserech zajistit, aby se při tisku dlouhých tabulek, které se nevejdou na jednu stránku, vytiskly určité jejich řádky na každé stránce (záhlaví a zápatí).

Stačí využít sekce tabulky thead a tfoot a nastavit jim ty správné styly.

 ...
<style type="text/css">
   thead {display: table-header-group;}
   tfoot {display: table-footer-group;}
</style>
...
<table>
   <thead>
      <tr>
         <td>Hlavička 1</td>
         <td>Hlavička 2</td>
      </tr>
   </thead>
   <tbody>
      <tr>
         <td>123</td>
         <td>456</td>
      </tr>
      ...
   </tbody>
   <tfoot>
      <tr>
         <td>Patička 1</td>
         <td>Patička 2</td>
      </tr>
   </tfoot>
</table>

FTP: Nastavení práv na složku pro příchozí soubory – /incoming

Poměrně často dostávám dotaz, jak nastavit práva na FTP složku pro příchozí soubory – složku /incoming (i když já doporučuji jiný název, viz dále).

V podstatě chceme dosáhnout toho, aby anonymní uživatel:

  • mohl do složky libovolně přidávat soubory,
  • mohl zobrazovat seznam souborů ve složce – chceme mu dát možnost, aby si ověřil, že jeho soubor byl úspěšně nahrán,
  • nemohl ze složky soubory mazat – nechceme přeci, aby nám kdokoli mohl smazat soubory určené pro nás,
  • nemohl ze složky číst – nejenom, že nechceme, aby nám někdo cizí mohl číst soubory určené pro nás, ale navíc bychom si povolením čtení připravili hodně krušné chvíle – roboti totiž pravidelně procházejí FTP servery a přímo vyhledávají takovéto složky, kde je povolen zápis a čtení – v případě úspěchu se velmi rychle stanete uložištěm pro warez, videa a jiné nechtěné soubory.

Právě z posledního uvedeného důvodu, kdy se roboti zaměřují převážně na složku /incoming a ostatní složky pro zrychlení ignorují, doporučuji pojmenovat složku pro příchozí soubory jinak. V českém prostředí například /prichozi.

A teď jak na práva:

  • práva budeme nastavovat účtu anonymního uživatele (obvykle IUSR_SERVER),
  • práva aplikujeme přímo na složku pro příchozí soubory – /incoming, /prichozi,
  • nevystačíme si se standardní záložkou Vlastnosti ~ Zabezpečení, ale musíme dát „Upřesnit“,
  • vytvoříme pro anonymního uživatele pravidlo, kde Použít pro nastavíme na Tato složka a podsložky (standardně je to totiž Tato složka, podsložky a soubory) apovolíme vše mimo
    • Full Control – Úplné řízení,
    • Delete Subfolders and Files – Odstraňovat podsložky a soubory,
    • Delete – Odstraňovat,
    • Change Permissions – Měnit oprávnění
    • a Take Ownership – Přebírat vlastnictví.
  • ještě můžeme zvážit práva Zapisovat atributy, popř. Zapisovat rozšířené atributy, ale to už celkem není nic proti ničemu.
  • musíme si dát také pozor, jestli se nám z nadřazené složky nedědí nějaká další práva, ale nebývá to obvyklé (spíše by se dalo považovat za chybu, kdyby anonymní uživatel měl na kořenovou složku FTP serveru nějaká práva, která by se dědila na podsložky).

Tím, že pravidlo aplikujeme pouze na objekty typu složka, nikoliv na soubory, dosáhneme právě toho, že uživatel sice všechno uvidí, ale nebude moct číst jednotlivé soubory.

Také si dejte pozor, jestli Vám nevznikají práva k souborům nějakým jiným děděním, klasickou chybou jsou třeba práva pro CREATOR OWNER.

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.

Připojení Outlooku RPC over HTTP(S) na Exchange

Na netu jsou stovky návodů, jak nakonfigurovat RPC over HTTP komunikaci Outlooku s Exchange serverem, např.

Návody jsou to celkem srozumitelné, nicméně může nastat mnoho zásadních zádrhelů, které návody nedostatečně zmiňují, či málo zdůrazňují. Některé, na které jsem narazil já:

Certifikát k HTTPS spojení musí být důvěryhodný

Pokud používáme k HTTPS certifikát generovaný vlastní certifikační autoritou (CA), pak musíme na klientském počítači zajistit, aby připojení k serveru probíhalo čistě, bez jakéhokoliv promptu Internet Exploreru na certifikát. Outlook totiž tento prompt nepodporuje a spojení zamítne.

V podstatě je potřeba nainstalovat certifikát na klienta, třeba přístupem na https://server/rpc/rpcproxy.dll. Musíme dosáhnout stavu, aby se nás to ptalo jen na heslo, na nic jiného. Nainstalovaný certifikát nám však bude fungovat jen v případě, že máme instalovaný i certifikát CA – ten je v členských počítačích domény instalován automaticky (pokud CA používá AD), na nečlenských počítačích je potřeba ho instalovat samostatně.

Pozor – narazil jsem na problém, kdy IIS mělo přidělený certifikát, ale mezi tím došlo k reinstalaci CA. Certifikát na IIS tak neodpovídal kořenovému certifikát CA. Internet Explorer to navíc pojal po svém. Místo, aby ohlásil neplatný certifikát, tak po instalaci kořenového certifikátu na jakýkoliv HTTPS požadavek vůči příslušnému serveru odpovídal chybou „Server nebyl nalezen nebo došlo k chybě v systému DNS.“ (jako kdyby server neexistoval).

Global Catalog na jiném serveru

Pokud máme scénář s jedním Exchange serverem, na kterém navíc běží i doménový řadič (DC), může se zdát, že není co řešit. Zásadní komplikace však nastane, pokud máme v doméně více DC a Global Catalog je na jiném serveru, než Exchange.

Který server je Global Catalogem poznáme v konzoli Active Directory Sites and Services, pokud si nalistujeme příslušný server, položku „NTDS Settings“ a k ní Properties – tam je zaškrtávací políčko Global Catalog. Jak přesunout GC na jiný server je mimo dosah tohoto článku.

Pokud tedy máme GC na jiném serveru, nestačí nám standardní konfigurace portů (hodnota ValidPorts), protože potřebujem na port 6004 dostat právě process lsass.exe z GC.

Jedna možnost je přesunout GC na stejný server k Exchange. Druhou možností je nastavit klíč registru „NSPI interface protocol sequences“ i na GC server a na exchange serveru nastavit v klíči „ValidPorts“ port 6004 na GC server (NetBIOS jméno i FQDN). V Outlooku pak jako server exchange vyplníme NetBIOS název GC serveru.

Pozor na to, že nám ve standardním nastavení funguje dokonce i RPCPING, protože si server s exchange na port 6004 nasadí vlastní proces lsass.exe. Nicméně Outlook zahájí komunikaci a nic se nepřipojí. V logu webové služby jsou též korektní řádky RPC_IN_DATA a RPC_OUT_DATA se success kódem 200.

Troubleshooting nástroje a doporučení

  • spouštět Outlook s parametrem outlook /rpcdiag (též Ctrl+RClick na tray-ikonu Outlooku a volba Connection Status…)
  • telnet localhost 6001,  6002, 6004 (ze serveru) dává banner „ncacn_http/1.0“
  • netstat -ano (ze serveru) nám říká, jaké procesy obsluhují jaký portu (6001-store.exe, 6002-mad.exe, 6004-lsass.exe nebo mad.exe),
  • utilita rpcping z Windows Server Resource Kitu (z klienta),
  • utilita rpcdump z Windows Server Resource Kitu (z klienta i ze serveru),
  • adresa https://server/rpc/ se musí ptát na heslo a pak odpovědět 401.3,
  • adresa https://server/rpc/rpcproxy.dll se musí ptát na heslo a pak odpovědět prázdnotou,
  • v logu webové služby musí být záznamy RPC_IN_DATA a RPC_OUT_DATA, kód 200.
  • při nastavování accountu v Outlooku musí být v základním dialogu interní jméno serveru a až v nastavení RPC over HTTPS se dávají public jména serveru (ty se musí shodovat se jménem, na které je vystaven certifikát pro SSL)

VPN přípojení a zachování přístupu k internetu

Pokud používáme VPN klienta z Windows a připojujeme se k virtuální privátní síti (VPN), pak ve výchozím nastavení ztratíme po připojení k VPN přímý přístup k internetu, protože VPN klient nastaví default gateway na vzdálenou default gateway VPN serveru (i z důvodu zabezpečení VPN). Veškerá IP komunikace, která není explicitně routována jinak (což obvykle není), je tak směřována na default gateway VPN připojení – to buď požadavek vyřídí, nebo nevyřídí (často právě spíše nevyřídí a „jsme bez internetu“).

Každopádně, pokud chceme zachovat dosavadní gateway, stačí ve vlastnostech VPN připojení odškrnout položku „Use default gateway on remote network“, respektive„Používat výchozí bránu vzdálené sítě“. Tu najdeme na záložce Sítě, pod vlastnostmi protokolu TCP/IP a Upřesnit, ve starých Win9x to bylo pod Properties ~ Server Types ~ TCP/IP Settings.

Pozor však, že standardní volba má svojí logiku. Pokud se připojíme na VPN, stává se náš počítač rozhraním mezi firemní sítí a internetem a je tak potenciálně nebezpečným místem pro útoky.

Velikost okna browseru univerzálně

V jednotlivých prohlížečích se informace o aktuální velikosti okna zjišťuje dost různě, je tedy nutno použít určitou kaskádu pro „univerzální“ (alespoň trochu) vyhodnocení:

var winWidth, winHeight, d=document;
if (typeof window.innerWidth!='undefined')
{
   winWidth = window.innerWidth;
   winHeight = window.innerHeight;
}
   else
{
   if (d.documentElement && )typeof d.documentElement.clientWidth != 'undefined') && (d.documentElement.clientWidth != 0))
   {
       winWidth = d.documentElement.clientWidth;
       winHeight = d.documentElement.clientHeight;
   }
   else
   {
       if (d.body && (typeof d.body.clientWidth != 'undefined'))
       {
           winWidth = d.body.clientWidth;
           winHeight = d.body.clientHeight;
       }
   }
}

VS2005: Debugging – Unable to attach to process. Neplatný popisovač vazby.

Opravdu drsná chyba se mi projevila ve VS2005 a nebylo boha, abych k tomu na netu něco našel.
Když jsem chtěl ladit, tak mi to vyhazovalo chyby

Unable to attach to process. Neplatný popisovač vazby.
Unable to attach to process. The binding handle is invalid.

Když jsem zkoušel „Attach to process…“, tak to házelo

Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor 
named '<hostname>'. Neplatný popisovač vazby.

Unable to connect to the Microsoft Visual Studio Remote Debugging Monitor
named '<hostname>'. The binding handle is invalid.

…netušim, čím to přesně je, ale nějak to souvisí s Terminal Services, když je tato služba DISABLED. Pokud se povolí služba Terminal Services, vše funguje !!!

Jako další možný workaround údajně stačí vypnout přepínač „Enable the Visual Studio hosting process“ – Project Properties ~ Debug ~ … (osobně nemám ověřeno).