Firefox velmi pomalý při použití Windows Vista a ASP.NET Development serveru

Načítání webových stránek je pod Firefoxem v kombinaci s Windows Vista a ASP.NET Development serverem velmi pomalé. První podezření padlo na ASP.NET AJAX pod Firefoxem, nicméně pravda se ukázala být jinde.

Za všechno může automatické ladění TCP stacku ve Windows Vista. Na internetu jsem nalezl dvě řešení:

Zákaz automatického ladění TCP stacku…

… lze provést spuštěním „netsh interface tcp set global autotuninglevel=disabled“ z příkazové řádky.

Toto řešení jsem nezkoušel.

Zákaz IPv6 ve Firefoxu

Dle návodu pomůže ve Firefoxu:

  1. přejít na stránku about:config,
  2. vyhledat položku network.dns.disableIPv6,
  3. nastavit hodnotu na true.

Toto řešení bylo jednoduché a účinné.

Jaká je Vaše zkušenost s kombinací Firefoxu, Windows Vista a ASP.NET Development serveru? Nebo jste na stejný problém narazili i při jiné kombinaci produktů?

Programátorská hádanka – catch & throw

Jaký je rozdíl v prapagaci výjimky z bloku catch:

try
{
...
}
catch (Exception e)
{
   throw;
}

versus

try
{
...
}
catch (Exception e)
{
   throw e;
}

Odpověď je tentokrát jednoduchá a jako obvykle ji najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):

>>> Při vyhození výjimkt přes „throw;“ se nezmění stack trace – výjimka se stále tváří, že vzešla z původní metody. Při vyhození přes „throw e;“ je změněn stack trace, výjimka se tváří, že vzešla z naší metody obsluhující výjimku. <<<

Css styly a obrázky nefungují v IIS7

Instalace IIS7 ve Windows Vista obsahuje více možností než tomu bylo dříve. V dobré víře jsem si zvolil nástroje pro správu a podporu ASP.NET v minimalistické podobě.

Díky podpoře nainstalovené podpoře ASP.NET fungovaly webové aplikace, ovšem statický obsah (jako jsou právě obrázky nebo soubory kaskádových stylů) prohlížeč neobdržel. Při zadání libovolné URL na statický obsah (například i adresa k obrázku, který ani na disku není) prohlížeč nezobrazí nic (i zdrojový kód stránky je prázdný).

Aby IIS7 poskytoval statický obsah, je nutné mezi instalovanými komponentami IIS zvolit i „Statický obsah“.

Programátorská hádanka – Výjimka ve výjimkách

Jaký je rozdíl v zachytávání výjimek při použití typu výjimky Exception

try
{
...
}
catch (Exception e)
{
...
}

a bez použití tohoto typu, tedy

try
{
...
}
catch
{
...
}

Zdůrazňuji, že tento rozdíl existuje jen v .NET Frameworku 1.x, ve verzi 2.0 jsou způsoby funkčně rovnocenné.

Odpověď najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):

>>> Konstrukce catch (Exception e) zachytává jen CLS-compliant výjimky, catch zachytává všechny chyby. Praktický rozdíl je při zachytávání chyb z COM objektů, jejichž chyby nejsou CLS-compliant výjimkami. .NET Framework 2.0 tyto chyby z COM objektů zabalí do RuntimeWrappedException, které jsou CLS-compliant, takže je chyba zachycena i při použití catch (Exception e).  <<<

…a jako posledně: teď se přiznejte, kdo jste to znal!

Lokalizace skinů (Themes)

Nevím jak vás, ale mně už delší dobu rozčiluje, že skiny nejsou lokalizovatelné. Pánové z Microsoftu na to buď zapomněli, nebo nevím, každopádně se tak skiny staly do značné míry nepoužitelnými pro lokalizované aplikace.

Štvalo mě to tak dlouho, až jsem napsal vlastní lokalizaci skinů. Není to nic překrásného, protože jen máloco je v ASP.NET tak zapouzdřeno jako témata a skiny a tak se kód hemží reflexí – nicméně to výborně funguje k tomu účelu, na který to sám potřebuji – do lokalizovaných webových aplikací s běžným provozem bez masové zátěže.

Optimalizace opět ponechám na každém z vás, stejnětak případný příhodnější způsob integrace do ASP.NET. Níže popisuji jednoduché řešení, jehož účelem je pochopení použitého způsobu lokalizace skinů. Taktéž předesílám, že se jedná o explicitní metodu lokalizace, protože implicitní lokalizaci nepovažuji za obecně vhodnou.

Jak lokalizace skinů funguje

První, co každý asi zkusí, je použít ve skinu běžnou expression <%$ Resources: MyResources, MyValueName %>. ASP.NET Vás odmění krásnou chybovou hláčkou parseru: „Expressions are not allowed in skin files.“ Tudy cesta nevede…

Inspiroval jsem se tedy v syntaxi lokalizace, kterou používá XmlSiteMapProvider pro web.sitemap soubory, tedy např.:

&lt;siteMapNode url=&quot;~/default.aspx&quot; title=&quot;$resources:MyResources,MyResourceKey,My default value&quot; /&gt;

a řekl jsem si, že stejnou syntaxi bych mohl použít i pro lokalizaci skinů:

&lt;asp:Button Text=&quot;$resources: MyResource, MyValue, Default Value&quot; runat=&quot;server&quot; /&gt;
&lt;asp:Button SkinID=&quot;SkinWithDefault&quot; Text=&quot;$resources: MyResource, InvalidResource, Default Value&quot; runat=&quot;server&quot; /&gt;

…samozřejmě se tím odepře možnost použití textů začínajících znaky „$resource:“, ale to mi nevadí.

Teď ještě vymyslet, kde a jak v životním cyklu stránky zajistit odchytávání a vyhodnocování těchto lokalizačních výrazů. Po chvilce bádání s Reflectorem lze dospět k následujícím poznatkům:

  1. téma stránky je uložena v private fieldu Page._theme typu PageTheme
  2. stylesheet-téma stránky je uloženo v private fieldu Page._styleSheet typu PageTheme
  3. témata se inicializují (InitializeThemes) po ve fází PreInit životního cyklu stránky, avšak až po události PreInit (v obsluze události PreInit tedy zřejmě není možné témata modifikovat),
  4. témata se aplikují ve fázi Init životního cyklu stránky, avšak před událostí Init (v obsluze události Init je tedy pozdě je modifikovat),
  5. třída PageTheme má metodu ApplyControlSkin(Control control), která najde ve slovníku skinů skin (typ ControlSkin) příslušející danému controlu a aplikuje ho pomocí skin.ApplySkin(Control control)
  6. metoda ControlSkin.ApplySkin(Control control) nedělá nic jiného, než že zavolá vygenerovaného delegáta.
public void ApplySkin(Control control)
{
    this._controlSkinDelegate(control);
}

Zdánlivě nikde není rozumný prostor pro modifikaci skinu, nikde není seznam hodnot skinu, žádná property-value kolekce, kde by se dal resource-odkaz najít a modifikovat. Vygenerovaný delegát prostě natvrdo nastavuje vlastnosti příslušného controlu a hotovo.

…zde však přichází to kouzlo! Můžeme totiž delegáta, který je uložen v private fieldu _controlSkinDelegate vyměnit za delegáta vlastního. A to nejenomže ho můžeme vyměnit, ale můžeme si dokonce odkaz na původního delegáta uchovat a tuto původní metodu z našeho delegáta zavolat. Celý můj fígl tedy spočívá ve výměně tohoto delegáta, za jinou metodu, která však v sobě nejprve volá metodu původní (aplikuje skin) a následně prochází skinovatelné property controlu (pro jednoduchost pouze textové) a hledá v nich lokalizující resource-odkaz, který případně vyhodnotí. Abychom si měli původního delegáta jak uchovat, je nová metoda wrapována do instance třídy SkinLocalizationDelegateWrapper.

Poslední, co zbývá vyřešit, je jak se s vlastní obsluhou dostat do správného místa životního cyklu stránky, tedy za InitializeTheme(), které je po události PreInit a před ApplySkin(), které je na začátku Init fáze, před událostí Init. Nehledal jsem dlouho, a možná najdete vhodnější místo, ale já jsem pro tyto účely znásilnil virtuální metodu Control.ResolveAdapter(), která se volá na začátku InitRecursive(), tedy přesně v místě, kde se nám to hodí.

SkinLocalizationPageBase

Vytvořil jsem tedy nakonec třídu SkinLocalizationPageBase, která je potomkem System.Web.UI.Page, a která by měla být předkem všech stránek webu, kde lokalizované skiny používáme (nejlépe tedy úplně všech, pomocí <pages pageBaseType=“SkinLocalizationPageBase“ /> ve web.configu.

Výsledný kód vypadá takto:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Web.UI.Adapters;
using System.Reflection;
using System.Collections;
using System.Globalization;
using System.Resources;

namespace Havit.Web.UI
{
    /// &lt;summary&gt;
    /// Bázová třída pro stránky, které obsahují controly s lokalizovatelnými skiny.
    /// &lt;/summary&gt;
    public class SkinLocalizationPageBase : Page
    {
        #region ResolveAdapter (override)
        /// &lt;remarks&gt;
        /// Metoda ResolveAdapter je jediné místo, které jsem našel, že se pomocí něj dá vykonávat nějaký kód
        /// mezi InitializeTheme() (po OnPreInit) a ApplyControlSkin (před OnInit).
        /// &lt;/remarks&gt;
        protected override ControlAdapter ResolveAdapter()
        {
// Theme
            FieldInfo themeFieldInfo = typeof(Page).GetField(&quot;_theme&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            PageTheme theme = themeFieldInfo.GetValue(this) as PageTheme;

            if (theme != null)
            {
                PropertyInfo controlSkinsPropertyInfo = typeof(PageTheme).GetProperty(&quot;ControlSkins&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                IDictionary controlSkins = controlSkinsPropertyInfo.GetValue(theme, null) as IDictionary;

                foreach (DictionaryEntry entry in controlSkins)
                {
                    ControlSkin controlSkin = entry.Value as ControlSkin;

                    if (controlSkin != null)
                    {
                        FieldInfo controlSkinDelegateFieldInfo = typeof(ControlSkin).GetField(&quot;_controlSkinDelegate&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                        ControlSkinDelegate controlSkinDelegate = controlSkinDelegateFieldInfo.GetValue(controlSkin) as ControlSkinDelegate;

                        SkinLocalizationDelegateWrapper sldw = new SkinLocalizationDelegateWrapper(controlSkinDelegate);

                        controlSkinDelegateFieldInfo.SetValue(controlSkin, new ControlSkinDelegate(sldw.DoWork));
                    }

                }
            }

            // StylesheetTheme
            FieldInfo styleSheetFieldInfo = typeof(Page).GetField(&quot;_styleSheet&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            PageTheme styleSheet = styleSheetFieldInfo.GetValue(this) as PageTheme;

            if (styleSheet != null)
            {
                PropertyInfo controlSkinsPropertyInfo = typeof(PageTheme).GetProperty(&quot;ControlSkins&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                IDictionary controlSkins = controlSkinsPropertyInfo.GetValue(styleSheet, null) as IDictionary;

                foreach (DictionaryEntry entry in controlSkins)
                {
                    ControlSkin controlSkin = entry.Value as ControlSkin;

                    if (controlSkin != null)
                    {
                        FieldInfo controlSkinDelegateFieldInfo = typeof(ControlSkin).GetField(&quot;_controlSkinDelegate&quot;, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
                        ControlSkinDelegate controlSkinDelegate = controlSkinDelegateFieldInfo.GetValue(controlSkin) as ControlSkinDelegate;

                        if (!(controlSkinDelegate.Target is SkinLocalizationDelegateWrapper))
                        {

                            SkinLocalizationDelegateWrapper sldw = new SkinLocalizationDelegateWrapper(controlSkinDelegate);

                            controlSkinDelegateFieldInfo.SetValue(controlSkin, new ControlSkinDelegate(sldw.DoWork));
                        }
                    }

                }
            }

            return base.ResolveAdapter();
        }
        #endregion

        #region SkinLocalizationDelegateWrapper
        /// &lt;summary&gt;
        /// SkinLocalizationDelegateWrapper obalí původní výkonný kód skinování a zajistí vyhodnocení resource-odkazů.
        /// &lt;/summary&gt;
        class SkinLocalizationDelegateWrapper
        {
            private ControlSkinDelegate _oldControlSkinDelegate;

            public SkinLocalizationDelegateWrapper(ControlSkinDelegate oldControlSkinDelegate)
            {
                this._oldControlSkinDelegate = oldControlSkinDelegate;
            }

            public Control DoWork(Control control)
            {
                Control result = null;

                if (this._oldControlSkinDelegate != null)
                {
                    result = this._oldControlSkinDelegate(control);
                }

                PropertyInfo[] properties = result.GetType().GetProperties();
                foreach (PropertyInfo property in properties)
                {
                    if (property.PropertyType == typeof(String))
                    {
                        // zajímají nás jen property typu string
                        ThemeableAttribute themableAttribute = Attribute.GetCustomAttribute(property, typeof(ThemeableAttribute)) as ThemeableAttribute;
                        if ((themableAttribute == null) || themableAttribute.Themeable)
                        {
                            // které nemají atribut themable vůbec, nebo ho nemají s volbou false (výchozí je true)
                            string propertyValue = property.GetValue(result, null) as string;
                            if (((propertyValue != null) &amp;&amp; (propertyValue.Length &gt; 10)) &amp;&amp; propertyValue.ToLower(CultureInfo.InvariantCulture).StartsWith(&quot;$resources:&quot;, StringComparison.Ordinal))
                            {
                                string resourceOdkaz = propertyValue.Substring(11);
                                if (resourceOdkaz.Length == 0)
                                {
                                    throw new ConfigurationErrorsException(&quot;Resource odkaz skinu nesmí být prázdný.&quot;);
                                }
                                string resourceClassKey = null;
                                string resourceKey = null;
                                int length = resourceOdkaz.IndexOf(',');
                                if (length == -1)
                                {
                                    throw new ConfigurationErrorsException(&quot;Resource odkaz skinu není platný&quot;);
                                }
                                resourceClassKey = resourceOdkaz.Substring(0, length);
                                resourceKey = resourceOdkaz.Substring(length + 1);
                                string defaultPropertyValue = null;
                                int index = resourceKey.IndexOf(',');
                                if (index != -1)
                                {
                                    defaultPropertyValue = resourceKey.Substring(index + 1); // default value
                                    resourceKey = resourceKey.Substring(0, index);
                                }
                                else
                                {
                                    propertyValue = null;
                                }

                                try
                                {
                                    propertyValue = (string)HttpContext.GetGlobalResourceObject(resourceClassKey.Trim(), resourceKey.Trim());
                                }
                                catch (MissingManifestResourceException)
                                {
                                    // NOOP
                                }

                                if (propertyValue == null)
                                {
                                    propertyValue = defaultPropertyValue;
                                }

                                property.SetValue(result, propertyValue, null);
                            }
                        }
                    }
                }

                return result;
            }
        }
        #endregion
    }
}

Úplné demo v podobě Web Site je v připojeném ZIP souboru.

Související články

Office SharePoint Designer ničí odkazy v .master souborech

Dvě hodiny jsem právě strávil s obligátní chybou „File Not Found.“, problém se nakonec ukázal v tom, že Microsoft Office SharePoint Designer při otevření default.master souboru změnil v direktivách <%@ Register %> atribut src z

<%@ Register TagPrefix="wssuc" TagName="Welcome" src="~/_controltemplates/Welcome.ascx" %>

na

<%@ Register TagPrefix="wssuc" TagName="Welcome" src="_controltemplates/Welcome.ascx" %>

…zabít málo!!!

Zobrazování podrobností výjimek a chyb

Jestli Vás taky rozčilují chybové hlášky SharePointu ve stylu „Unexpected error has occured.“ nebo „File not found.“ a marně hledáte detaily těchto chyb/výjimek, pak vězte, že stačí ve web.configu nastavit v elementu SharePoint/SafeMode atribut CallStack na true:

<SharePoint>
    <SafeMode MaxControls="200" CallStack="true" ...

Samozřejmě k tomu ještě <customErrors mode=“Off“ /> nebo mode=“RemoteOnly“:

<customErrors mode="Off" />

…a je to.

Samozřejmě na produkční mašině bychom měli mít CallStack=“false“.

HttpException: The Controls collection cannot be modified because the control contains code blocks

Tuto krásnou výjimku můžeme znenadání dostat (mimo jiné), pokud budeme v controlu <head runat=“server“> používat code-bloky, např.

<head runat="server">
    <title>HAVIT Goran</title>
    <link rel="stylesheet" type="text/css" href="~/templates/styles/havit.css" />
    <link rel="stylesheet" type="text/css" href="~/templates/styles/global.css" />
    <script type="text/javascript" src="<%= ResolveUrl("~/templates/scripts/HavitScripts.js") %>"></script>
    <asp:ContentPlaceHolder ID="HeadtailCPH" runat="server" />
</head>

Onen ďábelský blok <%= … %> nemusí zpočátku vůbec vadit, pokud však někdy později do stránky přidáme funkcionalitu, která by chtěla měnit Page.Header, např. umístíme do stránky control, který si bude chtít přilinkovat přes Header vlastní styly a skripty, pak budeme obšťastněni krásnou výjimkou System.Web.HttpException:

The Controls collection cannot be modified because the control contains code blocks (i.e. <% … %>).

…lepší je tedy code-bloky <% %>, resp. <%= %> v <head runat=“server“> nepoužívat (ještě lépe je nepoužívat vůbec) a nahradit je jiným způsobem. V tomto konkrétním případě třeba za <%# ResolveUrl(…) %> data-bindovací konstrukci (samozřejmě pak musíme volat Page.Header.DataBind()).

Programátorská hádanka – neprobádaná zákoutí C#

Víte co znamená @ v následujícím bloku kódu?

ICollection @is = dataSource as ICollection;
if (@is != null)
{
     this.Items.Capacity = @is.Count + this.Items.Count;
}

Odpověď najdete o řádku níže napsanou bílým písmem (pro odtajnění třeba označte):

>>> Zavináč je možné použít na začátku identifikátoru, pokud chceme, aby identifikátorem mohlo být i klíčové slovo (is, null, for, …) <<<

…a teď se přiznejte, kdo jste to znal?

Nastavení portu služby Remote Desktop

Nastavení portu, na kterém poslouchá služba Remote Desktop, se nachází v registrech zde:

  • HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\TerminalServer\WinStations\RDP-Tcp\PortNumber