Author Archives: Robert Haken

avatar Neznámé

About Robert Haken

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

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?

Proč nejde udělat rozumný AjaxValidator?

Po půl dni snažení a bádání jsem dospěl k subjektivnímu závěru, že nelze udělat rozumný ASP.NET validátor založený na AJAXu. Přesněji řečeno bylo mým cílem vytvořit obdobu CustomValidatoru, který by měl běžný ServerValidate a navíc klientskou jscript část, která by pomocí AJAXového HTTP-requestu validovala hodnotu vůči tomuto ServerValidate.

Narazil jsem na následující zásadní překážky, které podle mě nelze snadno překonat:

  1. Současné ASP.NET validátory, resp. všechny jejich Microsoftí obslužné klientské skripty a API jsou nekompromisně synchronní. V okamžiku požadavku validace (obvykle před submitem stránky) jsou volány prosté validační funkce všech povolených validátorů a od těchto se očekává jen true/false dle výsledku validace. V současném konceptu validátorů nikde není rozumný prostor pro asynchronní operace či nějakého zásahu do průběhu validace. 
  2. Je prakticky nemožné rozumným způsobem provést synchronní AJAXové volání vůči serveru a stejnětak je nemožné rozumně toto synchronní volání simulovat nějakým blokováním threadu uvnitř validační funkce. Javascript nezná žádné sleep/wait/pause a veškeré snahy o jeho implementaci vždy končí v tupé smyčce zatěžující CPU ze 100% do okamžiku splnění nějaké podmínky (přijetí AJAXového callbacku, dosažení určitého času, naplnění čítače, atp.)

Pro současný koncept ASP.NET validátorů jsem dospěl k těmto závěrům:

  1. Protože je koncept založen především na jednorázové kontrole hodnot před submitem formuláře a má za účel tento submit povolit/zakázat, je oprávněně založen na synchronních operacích a pro jakékoliv dlouhotrvající operace či asynchronní volání zde není prostor a poměrně logicky se s nimi nepočítá.
  2. Asynchronní AJAXová validace vůči serveru by byla použitelná u konceptu průběžné validace, kdy by byly hodnoty kontrolovány ihned po jejich zadání (onchange) a takováto validace by díky svému zpoždění nemohla blokovat odeslání formuláře, spíše by sloužila jako proaktivní kontrola uživatelského zadání.
  3. Současný koncept validátorů připouští pouze zběsilá řešení se synchronizačním blokováním threadu v různých sleep smyčkách (byť s timeoutem), popř. „chováním nekompatibilní“ on-change implementace dle bodu 2.

Veškeré pokusy o implementace AjaxValidatoru, které jsem na netu viděl, trpěly jedním nebo několika nedostatky z výše uvedených a jejich praktická použitelnost se tak blíží nule (spíše se tak stávají přetěžovači serverů, než účinnými validátory).

Avšak! Pokud by se našel někdo, koho by napadlo, jak výše uvedená omezení účinně obejít a funkční AjaxValidator vytvořit, sem s myšlenkou a pochvala ho nemine… ;-)))

PS: Sám jsem dospěl k implementaci AjaxValidatoru, který měl nastavitelný Timeout a ve validační funkci po zavolání asynchronního AJAX requestu na ServerValidate čekal po dobu tohoto timeoutu na odezvu serveru (čekal = různé hnusné implementace zatěžujících smyček čekajících na nějaký příznak nebo timeout). Pokud do Timeoutu nebyl AJAXem výsledek od serveru získán, propustila klientská část validaci jako IsValid=true, a tak se to v případě submitu zvalidovalo na serveru, nebo v případě onchange validace dovalidovalo později asynchronně (až dorazil callback, tak se aktualizoval validátor). Výsledek mi přišel pro praxi nepoužitelný, už timeout 1s je pro ovládání webového formuláře nepříjemný, stránka nereaguje na odeslání formuláře okamžitě, proto jsem to celé zahodil… Můj poznatek je, že pokud validátorem chápeme to, co ASP.NET, tedy blokaci odeslání formuláře, tak přes AJAX cesta nevede, i kdyby to šlo bez toho cyklického pollingu s timeoutem. Leda by byla odezva v řádu milisekund, což při serverových validacích moc nehrozí (obvykle nějaký dotaz do DB na exists, atp.).

Šířka tlačítek v IE (input type=“button“)

V Internet Exploreru je docela alchymie vyrobit tlačítko, které je široké stejně jako text na něm.

<input type="button" value="Text tlačítka" class="button"/>
<asp:Button ID="MyButton" Text="Text tlačítka" class="button"/>

Co jinde funguje, v IE selhává:

.button {
    margin:0;
    padding:0;
}

Co však kupodivu pomůže, je nastavení:

.button{
    padding:0;
    width:auto;
    overflow:visible;
}

…někdy je lepší nebádat, proč tomu tak je.

Office: Změna product key

Po instalaci Office (2007, ale bude to stejné) mi byla odmítnuta aktivace, z té haldy našich MSDNek jsem vybral použité číslo. Aktivace byla odmítnuta, možnost změny product key pomocí UI jsem však nikde nenašel.

Pomůže tedy smazání následujícího klíče z registrů:

HKLM\SOFTWARE\Microsoft\Office\12.0\Registration\{GUID}\DigitalProductID

Při následném spuštění kterékoliv aplikace z Office se spustí Office-instalátor, který se zeptá na nový Product Key a následně se sice ptá na „instalaci“, nicméně ve skutečnosti udělá jen konfiguraci a PK je nastaven. Opakované spuštění Office aplikace vyvolá možnost aktivace s novým PK.

Update pro Office 2013

Product key pro Microsoft Office 2013 lze změnit bez zásahu do registrů. Z ovládacích panelů zvolit Programy a funkce, zde  najít Microsoft Office 2013, kliknout pravým tlačítkem myši, zvolit Změnit. V zobrazeném dialogu vybrat „Zadat kód Product Key“.

InsertingGridView – grid s řádkem pro přidávání nových položek

Motivace

GridView je šikovný control ASP.NET 2.0, který řeší spoustu nedostatků a neduhů starého dobrého DataGridu. Umí toho hodně, jednu věc však stále neumí – přidávání nových položek (INSERT):

image

Tudy ne

Na internetu je spousta pokusů o implementaci insertingu do GridView, nicméně drtivá většina z nich se omezuje jen na více či méně intenzivní znásilnění řádku Footer a umístění insertingových controlů do něj. Pokud pomineme hlavní nedostatek, že tím vznikne prapodivný hybrid, který má editaci v řádku typu DataControlRowType.Footer, namísto DataControlRowType.DataRow, a že nemůžeme mít Grid s footerem, pak i samotné použití těchto insert-gridů je zoufalé – obsah šablony EditTemplate je tupě kopírován do FooterTemplate, atp. Takovéto násilné řešení se mi nelíbí, tudy ne:

Tudy ano

Na internetu je vidět i několik málo nedotažených implementací, které přistupují k nové položce trochu jiným způsobem. Další možností vytvoření insertovacího řádku je totiž rozšíření datové sady zpracovávané pomocí GridView o „prázdnou“ položku a její editace obdobně jako každého jiného řádku.

Tento způsob se mi stal inspirací pro napsání vlastního InsertingGridView, které je samostatným uceleným controlem, potomkem GridView s možností přidávání položek insertovacím řádkem.

Základní princip

Můj InsertingGridView funguje tak, že si pomocí delegáta GetInsertRowDataItem pro insert-řádek vyžádá položku (prázdnou nebo předvyplněnou, objekt, DataRow, nebo cokoliv stejného typu jako ostatní položky gridu), kterou si během data-bindingu vloží na správné místo zpracovávané datové množiny a pracuje s ní v režimu DataControlRowType.DataRow a DataControlRowState.Insert.

Použití

Mým základním pravidlem pro programování reusable záležitostí je maximální důraz na vnější rozhraní a příjemný způsob použití, vnitřní implementace ač by měla být „hezká“, je až druhotnou záležitostí. Začněme tedy tím, čeho chceme dosáhnout, jak má použití takového InsertingGridView vypadat.

Samotnému controlu jsem přidal property AllowInserting=“true|false“, který funkčnost přidávání povoluje a property InsertRowPosition=“Top|Bottom“, která určuje, zda-li má být insertovací řádek v gridu nahoře nebo dole. Control pak v ASPX stránce může vypadat třeba takto:

<havit:InsertingGridView
    ID="MyGridView"
    AllowInserting="true"
    InsertRowPosition="Bottom"
    AutoGenerateColumns="false"
    runat="server"
>
    <Columns>
        <havit:GridViewCommandField
            ShowEditButton="true"
            ShowDeleteButton="true"
            ShowInsertButton="true"
            ValidationGroup="grid"
            CausesValidation="true"
        />
        <asp:BoundField HeaderText="Sloupec bez editace" DataField="Nazev" ReadOnly="true" />
        <asp:TemplateField HeaderText="Sloupec s editací">
            <ItemTemplate>
                <%# ((Objednavka)Container.DataItem).Cislo %>
            </ItemTemplate>
            <EditItemTemplate>
                <asp:TextBox ID="CisloTB" Text="<%# ((Objednavka)Container.DataItem).Cislo %>" runat="server" />
                <asp:RequiredFieldValidator ControlToValidate="CisloTB" ErrorMessage="xxx" ValidationGroup="grid" runat="server" />
            </EditItemTemplate>
        </asp:TemplateField>
    </Columns>
</havit:InsertingGridView>

…oproti standardnímu GridView jsem opravdu přidal jen property AllowInserting a InsertRowPosition, vlastního GridViewCommandField si zatím nevšímejte, zajišťuje jen zobrazení správných příkazů dle stavu řádku a dostaneme se k němu později.

Dále už jsem přidal jen dvě události klasického vzoru – RowInserting a RowInserted, prakticky stejného významu a funkčnosti jako RowUpdating a RowUpdated. Teoreticky bude i jejich obsluha do značné míry stejná a pokud si budete chtít zjednodušit život, můžete se ve své implementaci pro začátek bez nich i obejít.

Poslední, co InsertingGridView pro svou funkčnost potřebuje, je mít nějaký způsob získávání datové položky pro insert-řádek. Prostě mít nějaký způsob, jak získat prázdný/předvyplněný objekt typu Objednavka, pokud grid zobrazuje objednávky, nebo typu DataRowView, pokud pracuje s „neobjektovými“ daty z databáze. Prostě novou položku typu stejného jako jsou ostatní položky v datové sadě bindované na grid. Jako způsob získávání této položky jsem zvolil delegáta vtěleného v propertyGetInsertRowDataItem a příklad kódu stránky tak může vypadat takto:

protected override OnInit(EventArgs e)
{
  MyGridView.GetInsertRowDataItem += MyGridView_GetInsertRowDataItem;
  MyGridView.RowInserting += new new GridViewInsertEventHandler(MyGridView_RowInserting);
  MyGridView.RowUpdating += ...
  MyGridView.RowDeleting += ...
}

private object MyGridView_GetInsertRowDataItem()
{
  Objednavka obj = new Objednavka();
  obj.Cislo = "předvyplněná hodnota nového řádku";
  return obj;
}

private void MyGridView_RowInserting(object sender, GridViewInsertEventArgs e)
{
  GridViewRow row = MyGridView.Rows[e.RowIndex];
  TextBox cisloTB = row.FindControl("CisloTB") as TextBox;
  ...
}

…toť vše.

Pro použití insertingu tedy v podstatě musím jen nastavit AllowInserting=“true“, nastavit delegáta GetInsertRowDataItem vracejícího hodnotu pro nový řádek a obsloužit událost RowInserting.

Na tomto místě je důležité zdůraznit, že můj InsertingGridView nepodporuje deklaratorní data-binding pomocí DataSourceID, protože tuto metodiku obecně považuji za zhovadilost a pro produkční projekty prakticky nepoužitelnou. Plnou podporu DataSourceID by nebyl problém do InsertingGridView implementovat, ostatně je to celé jen o Copy&Paste z Reflectoru.

Základní schéma implementace controlu

Control InsertingGridView je implementován jako potomek klasického GridView, přičemž:

  1. V metodě override void PerformDataBinding(IEnumerable data), která zajišťuje data-binding, se na správné místo výchozí datové sady vloží datová položka pro nový řádek (její pozici si uložíme do property InsertRowDataSourceIndex) a zavolá se s touto rozšířenou datovou sadou base.PerformDataBinding(extendedData), který samotný data-binding provede.
    Správné místo pro vložení datové položky nového řádku musíme určit na základě property InsertingRowPosition a v případě povoleného stránkování i na základě čísla stránky. V případě stránkování musíme mimo datové položky pro nový řádek vkládat do datové sady i další dummy-položky, na začátek sady jednu pro každou stránku před aktuální stránkou a na konec sady jednu pro každou stránku za aktuální stránkou. To vše proto, aby nám běžné položky po stránkách neposkakovaly podle toho, jestli zrovna editujeme nebo insertujeme a abychom měli stále stejný počet stránek.
    protected override void PerformDataBinding(IEnumerable data)
    {
        if (AllowInserting)
        {
            if (GetInsertRowDataItem == null)
            {
                throw new InvalidOperationException("Při AllowInserting musíte nastavit GetInsertRowData");
            }
            ArrayList newData = new ArrayList();
    
            object insertRowDataItem = GetInsertRowDataItem();
            foreach (object item in data)
            {
                newData.Add(item);
            }
            if (AllowPaging)
            {
                int pageCount = (newData.Count + this.PageSize) - 1;
                if (pageCount < 0)
                {
                    pageCount = 1;
                }
                pageCount = pageCount / this.PageSize;
    
                for (int i = 0; i < this.PageIndex; i++)
                {
                    newData.Insert(0, insertRowDataItem);
                }
                for (int i = this.PageIndex + 1; i < pageCount; i++)
                {
                    newData.Add(insertRowDataItem);
                }
            }
            if (EditIndex < 0)
            {
                switch (InsertRowPosition)
                {
                    case GridViewInsertRowPosition.Top:
                        this.InsertRowDataSourceIndex = (this.PageSize * this.PageIndex);
                        break;
                    case GridViewInsertRowPosition.Bottom:
                        if (AllowPaging)
                        {
                            this.InsertRowDataSourceIndex = Math.Min((((this.PageIndex + 1) * this.PageSize) - 1), newData.Count);
                        }
                        else
                        {
                            this.InsertRowDataSourceIndex = newData.Count;
                        }
                        break;
                }
                newData.Insert(InsertRowDataSourceIndex, insertRowDataItem);
            }
            data = newData;
        }
        base.PerformDataBinding(data);
    
  2. V metodě override GridViewRow CreateRow(…), která zajišťuje vytvoření nového controlu GridViewRow představujícího jeden řádek gridu, zajistíme, aby se při vytváření řádku pro insertovací položku nastavit správný stav tohoto řádku na DataControlRowState.Insert – což zajistí nejenom použití edit-režimu pro řádek, ale i správné chování CommandFieldů, atp.
    protected override GridViewRow CreateRow(int rowIndex, int dataSourceIndex, DataControlRowType rowType, DataControlRowState rowState)
    {
            GridViewRow row = base.CreateRow(rowIndex, dataSourceIndex, rowType, rowState);
            // Řádek s novým objektem přepínáme do stavu Insert, což zajistí zvolení EditItemTemplate a správné chování CommandFieldu.
            if ((rowType == DataControlRowType.DataRow)
                && (AllowInserting)
                && (dataSourceIndex == InsertRowDataSourceIndex))
            {
                _insertRowIndex = rowIndex;
                row.RowState = DataControlRowState.Insert;
            }
            if ((_insertRowIndex < 0) && (rowIndex == (this.PageSize - 1)))
            {
                row.Visible = false;
            }
                return row;
    }
    
  3. Dále potřebujeme vytvořit zachytávání příkazu Insert a jeho správné zpracování s příslušným vyvoláním událostí RowInserting a RowInserted. To zajistíme v metodě override void OnRowCommand(GridViewCommandEventArgs e) a následně v nové metodě HandleInsert(…), která je klonem metody HandleUpdate(…) klasického GridView. Já používám HandleInsert() ve zjednodušené podobě bez podpory DataSourceID, pokud by po této podpoře někdo toužil, nechť si zkopíruje z Reflectoru GridView.HandleUpdate() a upraví ho na insert, logika obsluhy je totožná.
    protected override void OnRowCommand(GridViewCommandEventArgs e)
    {
        base.OnRowCommand(e);
    
        bool causesValidation = false;
        string validationGroup = String.Empty;
        if (e != null)
        {
            IButtonControl control = e.CommandSource as IButtonControl;
            if (control != null)
            {
                causesValidation = control.CausesValidation;
                validationGroup = control.ValidationGroup;
            }
        }
    
        switch (e.CommandName)
        {
            case DataControlCommands.InsertCommandName:
                this.HandleInsert(Convert.ToInt32(e.CommandArgument, CultureInfo.InvariantCulture), causesValidation);
                break;
        }
    }
    protected virtual void HandleInsert(int rowIndex, bool causesValidation)
    {
        if ((!causesValidation || (this.Page == null)) || this.Page.IsValid)
        {
            GridViewInsertEventArgs argsInserting = new GridViewInsertEventArgs(rowIndex);
            this.OnRowInserting(argsInserting);
            if (!argsInserting.Cancel)
            {
                GridViewInsertedEventArgs argsInserted = new GridViewInsertedEventArgs();
                this.OnRowInserted(argsInserted);
                if (!argsInserted.KeepInEditMode)
                {
                    this.EditIndex = -1;
                    this.InsertRowDataSourceIndex = -1;
                    base.RequiresDataBinding = true;
                }
            }
        }
    }
    
  4. Poslední, co stojí implementačně za zmínku, je úprava obsluhy události RowEditing. Potřebujeme, aby editace a inserting byly vzájemně výlučné, aby tedy při zahájení editace byl vypnut inserting a při ukončení editace naopak reaktivován:
    protected override void OnRowEditing(GridViewEditEventArgs e)
    {
        base.OnRowEditing(e);
    
        if (!e.Cancel)
        {
            this.EditIndex = e.NewEditIndex;
            if ((AllowInserting) && (this.InsertRowDataSourceIndex >= 0) && (this._insertRowIndex < e.NewEditIndex))
            {
                this.EditIndex = this.EditIndex - 1 ;
                this.RequiresDatabinding = true; 
            }
            this.InsertRowDataSourceIndex = -1;
            _insertRowIndex = -1;
        }
    }
    
  5. Ostatní části kódu jsou jen běžné implementační záležitosti. Jsou vytvořeny události RowInserting a RowInserted, k ním příslušné metody OnRowInserting a OnRowInserted. Pro události jsou vytvořeny třídy argumentů GridViewInsertEventArgs a GridViewInsertedEventArgs. Pro InsertRowPosition je enum GridViewInsertRowPostion. Potřeba je taky delegát GetInsertRowDataItemDelegate.
  6. Poslední záležitostí, která stojí za zmínku, je již zmiňovaný GridViewCommandField. Je to potomek klasického CommandFieldu. Ten sice má podporu Insertu, ale korektně se chová jen ve FormsView, nikoliv v GridView. V GridView totiž při nastavení ShowInsertButton=“true“ zobrazuje na každém ne-insertovém řádku i tlačítko „New“ a na insert-řádku tlačítko naopak „Cancel“. GridViewCommandField tedy není nic jiného, než modifikace CommandFieldu, která tyto dvě nežádoucí tlačítka nezobrazuje.
    Klasický CommandField je bohužel dost zapouzdřen a neumožňuje své chování příliš overridovat, takže GridViewCommandField a další nutné třídy DataControlButton, DataControlImageButton, DataControlLinkButton, atp., jsou jen spousty Copy&Paste z Reflectoru.

InsertingGridView Known Issues

  • Nepodporuje data-binding pomocí DataSourceID (neřeším, protože DataSourceID nesnáším).
  • Pravděpodobně selže v případě custom-pagingu dat, protože PerformDataBind() nyní předpokládá na vstupu úplnou datovou sadu všech stránek, nikoliv částečnou (Toto doladím, až to budu potřebovat, není to zas tak obvyklý scénář – ono už běžný paging+inserting v jednom gridu je v praxi neobvyklá kombinace).

Download

Ke článku jsou přiloženy úplné zdrojové kódy controlu InsertingGridView a souvisejících tříd, z ranných fází jeho vývoje. Článek i zdrojáky jsou zamýšleny  jen jako inspirace do bojů s Vaším vlastním gridem a přiložené zdrojáky nejsou přímo kompilovatelené.  InsertingGridView v nich dědí z našeho už dříve rozšířeného EnterpriseGridView, i když z jeho funkčnosti mnoho nevyužívá a pro nikoho by neměl být problém adaptace přímo na potomka GridView.

Feedback welcome

Netroufám si tvrdit, že výše uvedené řešení či dokonce jeho implementace jsou dokonalé. Proto uvítám jakékoliv náměty, které Vás napadnou při vlastní implementaci či použití…

Update 10/2013

GridView s touto funkčností do dnes velmi intenzivně používáme, jestli si však dobře uvědomuji, opravili jsme v kódu za tu dobu jeden nebo dva bugy. Pokud by Vám tedy tento kód nestačil jako inspirace a zasekli byste se na nějakém bugu, ozvěte se mi.

AJAX: Nefunkční validátory uvnitř UpdatePanelu

Microsoftu se podařil pozoruhodný kousek, standardní validátory ASP.NET 2.0 nefungují korektně uvnitř ajaxového UpdatePanelu – při změně obsahu panelu se původní validátory neodregistrují. Např. tak nefungují validátory v GridView editaci, ve wizzardech, atp. Obvykle vyskakuje krásná JScript chyba „null is null“.

Předchozí beta verze AJAXu to řešily upravenými verzemi validátorů, které korektně používaly ScriptManager ke své přeregistraci. V RTM verzi AJAXu však již tyto validátory nejsou a Microsoft se rozhodl, že je bude aktualizovat všem přes WindowsUpdate, nezávisle na AJAXu.

Ta horší zpráva je, že tak dosud neudělal, přestože AJAX už dávno releasoval.

…takže kdo nemá nervy čekat na opravené validátory, musí sám ručně (přes <tagMapping>) použít jejich aktualizované verze (nebo odtud), tak jak tomu bylo v betách.

$MyExpression: Argument, aneb jak na custom expressions (ExpressionBuilder)

Jistě jste si v ASP.NET 2.0 všimli nové vlastnosti, tedy používání expressions v markup-kódu. Vestavěny jsou tyto:

<%$ ConnectionStrings: Name %>
<%$ Resources: Glossary, Key %>
<%$ AppSettings: Key %>

…a my si ukážeme, jak lze jednoduše přidávat vlastní (uvádím pouze primitivní příklad, který má ukázat, že to jde, a kudy na to).

Vše je to v podstatě o tom, že vytvoříme třídu odvozenou od abstraktní báze ExpressionBuilder a implementujeme metodu GetCodeExpression(…) nebo metodu EvaluateExpression(…). Metda EvaluateExpression() vrací vyhodnocený výraz, zatímco metoda GetCodeExpression() vrací kód, který se má použít v přiřazení do property při kompilaci ASPX stránky.

Abychom vytvořený expression-builder použili v našem web, musíme ho ještě přihlásit ve web.configu.

Jak by tedy mohla taková nejjednoduší třída pro expression vypadat:

namespace MyNamespace
{
   [ExpressionPrefix( "MyExpression" )]
   public class MyExpressionBuilder : System.Web.Compilation.ExpressionBuilder
   {
      public override CodeExpression GetCodeExpression(
         BoundPropertyEntry entry,
         object parsedData,
         ExpressionBuilderContext context)
     {
         return new CodeSnippetExpression(entry.Expression);
     }
   }
}

ve web.config pak zavedeme takto:

<compilation debug="true">
      <expressionBuilders>
          <add expressionPrefix="MyExpression" type="Namespace.MyExpressionBuilder, MyAssembly"/>
      </expressionBuilders>
  </compilation>

a v kódu pak použijeme třeba takto:

<asp:Literal Text="<%$ MyExpression: MyClass.MyConst %>" runat="server" /> 
<asp:TextBox ID="PasswordTB" MaxLength="<%$ MyExpression: Uzivatel.Properties.Password.MaximumLength %>" runat="server" />
<asp:RegularExpressionValidator ValidationExpression=<%$ MyExpression: MyRegexPatterns.EmailStrict %>" ... />

Důležité je zdůrznit, že MyExpression v této podobě nedělá nic jiného, než že ASP.NET-compileru dosazuje do míst, kde máme MyExpression použit, příšlušný kód. Můžeme tedy postavit naprosto libovolný syntakticky korektní kus kódu, který je compileru předhozen za přiřazovací rovnítko obdobně jako by se jednalo o makro známé z jiných programovacích platforem, např.:

@__ctrl.MaxLength = ((int)(Uzivatel.Properties.Password.MaximumLength));

Mozilla FireFox: Nefunguje XHTML-validní image-map

Následující XHTML-validní image-map v Mozille nechodí:

<img id="reklama" src=".." width="200" height="600" alt="..." usemap="#mapa" />
<map id="mapa">
    <area shape="rect" coords="10,110,186,245" alt="..." href="..." target="_blank" >
</map>

Mozille nestačí <map id=“…“> nýbrž vyžaduje <map name=“…“>… :-(((

Demo: http://www.bernzilla.com/bugs/imagemap1.html

Rozdíl mezi COALESCE() a ISNULL()

1. COALESCE() je z ANSI/ISO standardu SQL32, kdežto ISNULL() je jen T-SQL rozšíření (Microsoft SQL Serveru).

2. ISNULL() má vlastní interpretaci, kdežto COALESCE() je pouze zkrácený zápis pro CASE strukturu a i se tak provádí:

CASE
    WHEN (expression1 IS NOT NULL) THEN expression1
    ...
    WHEN (expressionN IS NOT NULL) THEN expressionN
    ELSE NULL
END

3. Typ výsledku ISNULL() je vždy dle prvního parametru, typ výsledku COALESCE() odpovídá CASE struktuře, tj. pokouší se o maximální záběr ze všech parametrů (SQL Server Books Online: „Returns the highest precedence type from the set of types in result_expressions and the optional else_result_expression.„).

DECLARE @Test char(2)
SET @Test = NULL
SELECT ISNULL(@Test, 'abcde'), COALESCE(@Test, 'abcde')

Např. výše uvedený test vrací ‚ab‘, ‚abcde‘.

4. Vzhledem k převodu COALESCE() na CASE oproti vlastnímu provádění, je ISNULL() obvykle rychlejší.

5. ISNULL() pochopitelně bere pouze dva parametry, kdežto COALESCE() víc.