Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

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

NTBACKUP: Scheduled job failuje – médium není prázdné

Pokud vytvoříme pomocí wizzardu full backup job zálohování na médium – schedulovanej na každý den – a předpokládáme, že budeme prostě jen cyklicky měnit média – tak nám po prvním cyklu zálohy přestanou probíhat, po chvilce pátrání se dostaneme k chybové hlášce „médium není prázdné“.

V základním nastavení ntbackup totiž média nemaže, což lze považovat za rozumné výchozí nastavení, nicméně pro náš případ nevhodné. Pokud bychom to neměli jako scheduled task, aplikace by se nás na smazání zeptala, nicméně takto job sfailuje.

Řešením je přidání parametru /UM do příkazové řádky, kterou příslušný scheduled task provádí. Tím se vynutí smazání média před spuštěním zálohování.

Jak se zbavit namespaces (xmlns) v rootovém elementu XML při serializaci

Při běžné serializaci objektu do XML nám XmlSerializer vytvoří root-element, který má nastavené namespaces, např.

<rootElement xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

To odpovídá XML normě. Může však nastat situace, např. při generování RSS Feedu, kdy namespace definovat nechceme.

Fígl, jak se zbavit namespace definice, spočívá v předhození XmlSerializeru kolekce XmlSerializerNamespaces s jednou „prázdnou“ položkou:

XmlSerializer serializer = new XmlSerializer(typeof (RssFeed));
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", null);
StringWriter writer = new StringWriter(CultureInfo.CurrentCulture);
serializer.Serialize(writer, this, ns);

…a je to, výsledné XML bude mít kořenový element jen

<rootElement>
  ...
</rootElement>

SQL2005: Odmazávání starších záloh

Na SQL 2005 Serveru mě velice překvapilo, že pomocí základního maintenance-planu již nelze nastavit automatické odmazávání starších záloh, starších .BAK souborů.

Řešení je naštěstí snadné, i když ho bohužel pomocí wizzardu nedosáhneme. Stačí však ručně modifikovat (pravým tlačítkem – Modify, nebo jen double-click) wizzardem vygenerovaný maintenance-plan a přidat do něj novou úlohu – Maintenance Cleanup Task, v jehož vlastnostech pouze nastavíme v jaké složce máme zálohy, jestli se mají procházet i podsložky a jak staré zálohy se mají zlikvidovat.

Přidání je opravdu snadné, je to snad na tři kliknutí (přetáhnout z toolboxu, nastavit vlastnosti a navázat do workflow protažením příslušné šipky). Na zvážení administrátorů nechávám, jakou závislost čištění udělat na zálohování (poklikáním na vazbu můžeme volit Success, Error, nebo jen Completion). Teoreticky tedy můžeme čištění podmínit úspěšným zálohováním, aby nám po čase nezmizely staré soubory a nové nevznikaly.

Windows 2000 Server: Event ID 1202, SceCli, 0xd : The data is invalid.

Tento warning se nám může množit v Application event logu, po aplikaci šablony zabezpečení Basicdc.inf. Potíž je v tom, že se tato šablona odkazuje na proměnné %SYSVOL%, %DSDIT% a %DSLOG“, které však neexistují (existují jen během Dcpromo procesu).

Stačí tedy tyto environment variables vytvořit. Výchozí složky jsou následující

%SYSVOL% = C:\WINNT\SYSVOL
%DSDIT% = C:\WINNT\NTDS
%DSLOG% = C:\WINNT\NTDS

…a warning je pryč.

Nested Repeaters – vnořování repeaterů

Vnořit Repeatery se může zdát potíž, dokud poprvé neuvidíte, jak je to jednoduché. Celý fígl totiž spočívá v data-bindingu vnitřních repeaterů v obsluze události ItemDataBound vnějšího Repeateru.

V příkladu vnější Repeater iteruje přes všechny obory činnosti (kategorie, skupiny) a vnitřní Repeater zobrazuje položky (zde „zápisy do katalogu“) příslušející danému oboru činnosti (kategorii, skupině).

MyPage.aspx

<asp:Repeater ID="OboryCinnostiRepeater" runat="server">
  <ItemTemplate>
     
   <%# ((OborCinnosti)Container.DataItem).Nazev %>
   
   <asp:Repeater ID="ZapisyRepeater" runat="server">
    <ItemTemplate>
      <%# ((ZapisDoKatalogu)Container.DataItem).Jmeno %>
     </ItemTemplate>
   </asp:Repeater>
  
  </ItemTemplate>
 </asp:Repeater>

MyPage.aspx.cs

private void OboryCinnostiRepeater_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
   RepeaterItem item = e.Item;
  
   // zajímají nás jen datové řádky, ne hlavička ani patička
   if ((item.ItemType == ListItemType.Item) || (item.ItemType == ListItemType.AlternatingItem))
   {
    // najdeme si vnitřní Repeater
    Repeater zapisyRepeater = (Repeater)item.FindControl("ZapisyRepeater");
  
    // a nabidnujeme mu data příslušející položce (oboru)
    OborCinnosti obor = (OborCinnosti)item.DataItem;
    ZapisDoKataloguCollection = obor.GetZapisy();
    
    zapisyRepeater.DataSource = zapisyOboru;
    zapisyRepeater.DataBind();
   }
}

File upload – HttpException (0x80004005): Request timed out.

Při uploadu velkých souborů (relativně dle rychlosti spojení) se můžeme setkat s chybou

HttpException (0x80004005): Request timed out.

Jde v podstatě o to, že IIS tlačí do ASP.NET data průběžně dlouhou dobu a request na straně ASP.NET vytimeoutuje. Stačí však nastavit parametr executionTimeout ve web.configu na dostatečnou hodnotu a je po problému:

<httpRuntime
     maxRequestLength="10240"
     executionTimeout="36000"
/>

…maxRequestLength je volba, na kterou se obvykle nezapomíná a která mění základní limit 4 MB (4096 KB) pro maximální velikost requestu (zadává se v KB, executionTimeout je v sekundách).

Download

Podle informací uživatele P.L. z newsgroupy microsoft.public.cs.developer vyřešil atributexecutionTimeout obdobný problém i s padáním dlouhotrvajících downloadů.

onBeforeUnload – Potvrzovací dialog před odchodem ze stránky

V browseru, na stránkách, kde dochází k editaci záznamů, či jiné aktivitě, kterou je potřeba zakončit uložením či volbou nějakého tlačítka, se nám může hodit využít události onBeforeUnload k zobrazení potvrzovacího dialogu s dotazem, zde si uživatel opravdu přeje stránku opustit.

<html>
<head>
   <script type="text/jscript">
      // inicializace  
      g_blnCheckUnload = true;     
      function RunOnBeforeUnload()
      {
         if (g_blnCheckUnload)
         {
            window.event.returnValue = 'Text, který bude přidán do confirmačního dialogu';
         }
      }
      function bypassCheck()
      { 
         g_blnCheckUnload  = false; 
      }
   </script>
</head>
<body onBeforeUnload="RunOnBeforeUnload();">
   <a href="http://www.havit.cz">dotaz zobrazen</a>
   <a href="http://www.havit.eu" onClick="bypassCheck">dotaz nezobrazen</a>
</body>
</html>

Událost onBeforeUnload se volá nejenom na odkazech a tlačítkách, ale i při zavírání okna prohlížeče a prakticky veškerých událostech, kde by mělo dojít k opuštění stránky.

Funguje to minimálně v Internet Exploreru a FireFoxu.

Modifikace s hlídáním změn

Nakonec se mi podařilo rozchodit i rozumnou podobu výše uvedeného, kdy je potvrzovací dotaz zobrazen jen při změně formulářových dat (a je tedy potřeba změny uložit):

<html>
<head>
   <script type="text/jscript">
      // inicializace  
      g_blnCheckUnload = false;     
      function RunOnBeforeUnload()
      {
         if (g_blnCheckUnload)
         {
            window.event.returnValue = 'Text, který bude přidán do confirmačního dialogu';
         }
      }
      function bypassCheck()
      { 
         g_blnCheckUnload  = false; 
      }
      function setupCheck()
      {
         g_blnCheckUnload  = true; 
      }
      registerEvents()
      {
         for (i = 0; i < document.forms[0].elements.length; i++)
         {
            document.forms[0].elements[i].onchange = setupCheck;
         }
      }
   </script>
</head>
<body onLoad="registerEvents();" onBeforeUnload="RunOnBeforeUnload();">
   <form ...>
      <input .../>
      ...
   </form>
   <a href="http://www.havit.cz">dotaz zobrazen, jsou-li změny</a>
   <a href="http://www.havit.eu" onClick="bypassCheck">dotaz nezobrazen</a>
</body>
</html>

…další vylepšování je samozřejmě možné.

Update (PetrF): Pokud nějaké existující události onChange chceme zachovat

function registerEvents()
{
 for (i = 0; i < document.forms[0].elements.length; i++)
 {
   var elem = document.forms[0].elements[i];
   var fnOnChangeOld = (elem.onchange) ? elem.onchange : function () {};
   elem.onchange = function () { fnOnChangeOld(); setupCheck() };
 }
}

…nebo přes jQuery.

SessionPageStatePersister: Ukládání ViewState do Session

ViewState se standardně ukládá do formulářového hidden input-fieldu do stránky, posílá se tedy sem a tam na klienta (GET) a zpět na server (POST).

Díky mechanizmu page-adapterů a připraveného SessionPageStatePersister-u lze v ASP.NET velmi snadno přesměrovat ukládání ViewState do Session, tedy na stranu serveru. Primárně je tento mechanizmus určený pro použití se zařízeními (browsery), kde není možné nebo žádoucí view-state pomocí hidden-fieldu přenášet (PDA, mobily, atp.). Nic nám však nebrání rozšířit jeho využití na všechny stránky.

Potřebujeme pouze dvě věci:

1. Připravit page-adapter, který používá SessionPageStatePersister

Vysvětlovat mechanizmus control-adapterů je mimo rozsah tohoto článku – je to prostě něco, co dokáže poměrně dost modifikovat výchozí chování controlů (a stránka je control), například zajistit jiné renderování, nebo právě způsob ukládání ViewState.

Potřebný page-adapter bude vypadat takto:

public class PageAdapter : System.Web.UI.Adapters.PageAdapter
{
   public override PageStatePersister GetStatePersister()
   {
      return new SessionPageStatePersister(this.Page);
   }
}

…nejde o nic jiného, než říci, že se má použít připravený SessionPageStatePersister.

2. Aplikovat page-adapter pomocí .browser souboru

ASP.NET řekneme, že má příslušný page-adapter použít, tak, že modifikujeme/vytvoříme příslušný .browser soubor. Modifikovat lze buď globální nastavení ve složce <windir>\Microsoft.NET\Framework\<ver>\CONFIG\Browsers, nebo pro jednotlivou aplikaci vytvořit .browser soubor do složky ~/App_Browsers.

Soubor ~/App_Browsers/My.browser bude vypadat třeba takto:

<browsers>
   <browser refID="Default">
      <controlAdapters>
         <adapter controlType="System.Web.UI.Page" adapterType="MyNamespace.PageAdapter"/>
      </controlAdapters>
   </browser>
</browsers>

…to je prakticky vše, co musíme udělat pro ukládání ViewState do Session.

Alternativa – overrride Page.PageStatePersister

Pokud máme ve svém projektu zavedenu společnou bázovou třídu všech stránek (což každopádně i jinak doporučuji), pal můžeme místo PageAdapteru můžeme i rovnou overridování property PageStatePersister:

protected override PageStatePersister PageStatePersister
{
    get
    {
        if (_pageStatePersister == null)
        {
            _pageStatePersister = new SessionPageStatePersister(this.Page);
        }
        return _pageStatePersister;
    }
}
private PageStatePersister _pageStatePersister;

…nevýhodou je pevné zakomponování volby Session jako ViewState uložiště do aplikace, na rozdíl od předchozí konfigurační možnosti nad hotovou aplikací.

POZOR!!! Od property PageStatePersister se nedokumentovaně očekává, že ji bude během jednoho requestu možno volat opakovaně a stále bude vracet stejnou instanci! Zatímco metoda PageAdapter.GetStatePersister() je zvnějšku obalena cachováním instance, v property si tento mechanizmus musíme zajistit sami pomocí private fieldu.

Konfigurace SessionPageStatePersisteru

SessionPageStatePersister se dá konfigurovat z web.configu pomocí elementu <sessionPageState />:

<system.web>
  <sessionPageState historySize="9" />
</system.web>

Jediným nastavitelným atributem je historySize, kterým se volí počet ViewState záznamů, které má persister udržovat. Výchozí hodnota je 9.

Úskalí použití SessionPageStatePersisteru

  • ViewState je ukládán jako položky Session + existuje slovník, který udržuje jaké klíče v Session odpovídají jakému požadavku.
  • Výchozí velikost tohoto slovníku je 9 záznamů, lze však změnit pomocí konfigurace.
  • Každý požadavek vytvoří nový záznam, desátý požadavek vytěsní první.
  • Pokud si tedy uživatel otevře více než 9 oken, pak načtení view-state selhává!!! Metoda není tedy ve výchozím nastavení příliš vhodná pro stránky s frames, nebo různá dialogová okna.
  • Ztrátou Session ztratíme i ViewState, pokud tedy máme například InProc session a restartujeme webovou aplikaci, ViewState je pryč.

Východiskem z některých situací je detekce ztráty ViewState.

IIS6: Volba výchozích regionálních nastavení pro ASP stránky

Můžeme se dočkat nepříjemností, pokud v ASP stránkách spoléháme na konkrétní regional-settings. Přesuneme-li aplikaci na server jiné lokalizace, nepomůže nám totiž ani nastavení Control Panel ~ Regional Settings (Ovládací panely ~ Regionální nastavení).

Je v podstatě několik možností, jak správný region vnutit, nicméně na IIS5.1+ mi jako nejlepší přijdezměna hodnoty AspLCID v IIS metabázi:

\\LM\W3SVC\AspLCID

Pro české regionální nastavení je správná hodnota 1029, hodnoty ostatních uvádí Microsoftí List of Locale ID (LCID).

Související zdroje informací:

Propojení databázového uživatele na login (sp_change_users_login)

Při přesunech databází mezi servery, obnovování ze záloh a podobných úkonech se nám může stát, že se ztratí propojení mezi databázovým uživatelem (User) a jeho loginem (SQL Server login). Pomocí běžných management-nástrojů pak nelze toto propojení obnovit.

Propojení obnovíme pomocí stored procedury sp_change_users_login:

USE mydb
  
-- Auto_Fix, pokud mají user i login stejné jméno, pokud login není, bude vytvořen
-- můžeme přidat i parametr @Password, který se použije, pokud bude login zakládán nově
EXEC sp_change_users_login @Action='Auto_Fix', @UserNamePattern='user'
  
-- Update_One použijeme, pokud se nám jména neshodují
EXEC sp_change_users_login @Action='Update_One', @UserNamePattern='user', @LoginName='username'

…tuto metodu nelze použít pro Windows-loginy, pouze pro SQL Server loginy.