Tag Archives: WebForms

ASP.NET WebForms – Dependency Injection s Castle Windsor [Ondřej Václavek, HAVIT Vzdělávací okénko, 25.1.2019]

Záznam ze Vzdělávacího okénka HAVIT ze 25. ledna 2019, kde Ondra Václavek povídal o našem adaptéru pro použití Castle Windsor containeru s ASP.NET WebForms (s pomocí přímé podpory Dependency Injection, které přinesl .NET Framework 4.7.2).

Popisovanou knihovnu Havit.CastleWindsor.WebForms naleznete zde:

Nahrávka je publikována na našem HAVIT YouTube Channelu.

WebForms: Když nefunguje asynchronní AutoPostBack na RadioButtonech

Pokud bojujete s nefunkčním AutoPostBack na asp:RadioButton v ASP.NET WebForms při asynchronním postbacku, pak je to způsobeno tím, že ASP.NET nerenderuje k RadioButtonu označenému jako Checked klientskou obsluhu události onclick. Zřejmě je to by-design ochrana proti vícenásobné obsluze změn, tedy aby se událost vykonala pouze na jednom z radiobuttonů ve skupině, ne na tom označovaném i odznačovaném.

Možnosti řešení jsou tedy dvě:

  1. Zahrnout i RadioButtons do UpdatePanelu, aby se přerenderovaly a obsluhy událostí správně přenastavily.
  2. Obskurní work-around, který vyrenderuje obsluhu na všech RB taky může být
CisloSmlouvyNoveRB.InputAttributes["checked"] = "true";

CS1647: An expression is too long or complex to compile (workaround)

Na jednom z projektů jsme po úpravě ASPX stránky s enormním objemem html kódu narazili na zajímavou chybu zobrazenou v kompilátorem: CS1647: An expression is too long or complex to compile. Velmi pozoruhodný je též popis chyby na MSDN a doporučené řešení.

Jako funkční workaround zafungovalo doporučení z blogu henrywrites a sice vložit do ASPX stránky serverový komentář, například

<%
// workaround pro CS1647: An expression is too long or complex to compile
%>

ASP.NET WebForms Scaffolding – Slides a dema [TechEd Praha 2013]

Slides a dema z mé přednášky na TechEd DevCon Praha 2013:

Videozáznam z této přednášky nebyl pořizován.

Juice UI – ASP.NET WebForms Controls pro jQuery UI

Na první dnešní přednášce ASP.NET 4.5 na Microsoft MVP Summitu byla prezentována zajímavá nová knihovna, která přináší jQuery UI Widgets do prostředí ASP.NET WebForms – Juice UI. Protože je to jedna z mála věcí, které nejsou pod NDA, mohu se s Vámi o tento poznatek podělit.

Juice UI aspiruje na náhradu AJAX Control Toolkitu a zahrnuje například tyto controly:

  • Accordition
  • Autocomplete
  • Button
  • DatePicker
  • Dialog
  • Draggable
  • Position
  • ProgressBar
  • Resizable
  • Slider
  • Sortable
  • Tabs

Použití je přímočaré a nijak nekomplikované, např.:

<Juice:Tabs ID="_Default" runat="server">
  <Juice:TabPage Title="Tab 1" ID="_Tab1">
    <TabContent>
      ...
    </TabContent>
  </Juice:TabPage>
  <Juice:TabPage Title="Tab 2" ID="_Tab2">
    <TabContent>
      ...
    </TabContent>
  </Juice:TabPage>
</Juice:Tabs>

…uvidíme, jak se vypořádají s porodními bolestmi, které AJAX ControlToolkit měl. Jedná se o open-source knihovnu použitelnou pod licencemi MIT nebo GPLv2.

Detailní ASP.NET Request + WebForm Page LifeCycle diagram

Pokud se zabýváte technologií ASP.NET do hloubky, může se Vám hodit můj „ASP.NET 2.0/3.5 Request + Page LifeCycle Diagram“:

ASP.NET LifeCycle 2

Jedná se o první verzi mého diagramu, který hodlám dále graficky vylepšovat a zpřehledňovat. Až mi zbyde chvilka, dám sem i PDF verzi k tisku. Stejnětak je možné, že jsem v něm někde udělal chybu. Pokud tedy nějakou nesrovnalost objevíte, dejte mi prosím vědět.

Červeně jsou označena místa, kde se lze zapojit s vlastní invencí, jde buď o události, virtuální metody, nebo adapter. Modře jsou vyznačeny interface pro implementaci dané funkčnosti a šedě jsou interní implementace ASP.NET.

Viz též

Detekce ztráty ViewState při použití SessionPageStatePersisteru

Pokud směřujete ViewState do Session pomocí SessionPageStatePersisteru, pak se dříve nebo později setkáte s problémem ztrát ViewState a s potřebou detekovat a řešit tuto situaci.

Ke ztrátě ViewState může dojít v následujících případech:

  • v případě zániku/vyčištění Session, zejména při jejím timeoutování při nečinosti uživatele – uživatel si například nechá dlouho otevřenou stránku, vyprší mu mezi tím session, následně provede postback a problém je na světě, došlý postback nemá na serveru už ViewState,
  • v případě ztráty InProc Session díky restartování aplikace (deployment nové verze, recyklace poolu, atp.)
  • v případě vypadnutí stránky z fronty SessionPageStatePersisteru – persister omezuje totiž počet uložených ViewState a při uložení dalšího nejstarší vypadávají. Výchozí délka fronty je 9, je možné ji přenastavit na větší ve web.configu elementem sessionPageState, atributem historySize
    • pokud si například uživatel otevře současně 10 oken aplikace, pak pokud se vrátí k prvnímu oknu a provede postback, opět chybí ViewState,
    • pokud uživatel používá tlačítko Zpět browseru, např. pokud máme na stránce grid a každý řádek má na sobě tlačítko Detail, jehož obsluha události dělá redirect na stránku s detaily. Takový uživatel pokud klikne na detail prvního řádku, pak Zpět, druhého řádku, Zpět, … kliknutím na detail desátého řádku už způsobený postback na serveru nemá svůj ViewState (devět ViewState detailů vytlačilo z fronty ViewState stránky s gridem) a problém je zase na světě.

Jak takovou situaci detekovat? …overridováním metody LoadPageStateFromPersistenceMedium() v našich stránkách (obvykle pomocí společného předka všech stránek, nějakého PageBase):

protected override object LoadPageStateFromPersistenceMedium()
{
    // base implementace vrací new Pair(pageStatePersister.ControlState, pageStatePersister.ViewState);
    object pageState = base.LoadPageStateFromPersistenceMedium();

    // ztrátu ViewState můžeme tedy detekovat jako (pair.Second == null)
    // metoda LoadPageStateFromPersistenceMedium se volá jen pro IsPostBack
    Pair pair = pageState as Pair;
    if ((pair != null) && (pair.Second == null))
    {
        Trace.Warn("ViewStateLost !!!");

        FormsAuthentication.RedirectToLoginPage();
    }

    return pageState;
}

Základní (base) implementace metody LoadPageStateFromPersistenceMedium() totiž nedělá celkem nic jiného, než že z použitého persisteru získá ControlState, ViewState a udělá z nich Pair.

Situaci můžeme řešit například tím, že uživatele požádáme o nové přihlášení, či provedeme redirect kamsi do rootu nebo sami na sebe. ViewState už nikde nevydolujeme, musíme z toho nějak vybruslit, aniž bychom uživatele obšťastnili výjimkou NullReferenceException, InvalidFormatException a jinými podobnými, které v takových situacích nastávají.

Pozor na Response.Redirect(), Response.End() a obsluhu výjimek

Jak myslíte, že dopadne následující příklad po kliknutí na tlačítko? (Stránka obsahuje jen label MyLabel a button MyButton)

public partial class _Default : System.Web.UI.Page 
{
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        MyLabel.Text = (string)Session["OK"];
    }

    void MyButton_Click(object sender, EventArgs e)
    {
        try
        {
            Session["OK"] = "ok";
            Response.Redirect("~/");
        }
        catch
        {
            Session["OK"] = "exception";
        }
    }
}

Mnohé z Vás asi překvapím, když řeknu, že do Session[„OK“] se uloží „exception“ a ten se v dalším requestu i zobrazí.

Response.Redirect(), resp. metoda Response.End(), kterou Redirect sám volá, totiž funguje tak, že vyvolá ve webové aplikaci interní výjimku (Thread.CurrentThread.Abort()), která je samotnou webovou aplikací zpracovávána tak, aby bylo dosaženo kýženého efektu, tj. aby se vykonávání kódu zastavilo v daném místě a další kód se nevykonal (resp. tato ThreadAbortException se na konci catch-bloků vyvolává znovu a jsou vykonány všechny příslušné catch/finally bloky a tedy např. i Page.OnUnload()).

Potíž však nastane v okamžiku, kdy sami obalíme volání Response.Redirect() či Response.End() zachytáváním výjimek a nespecifikujeme dostatečně typ výjimek, které chceme zachytávat. Pokud necháme chytat výjimky všechny, uvedeme jako typ Exception, pak se dočkáme nežádoucího efektu, kdy nám volání Response.Redirect()/End() způsobí vykonání obsluhy výjimky, blok catch.

Východiskem je tedy obsluhovat pouze specifické typy výjimek, tak, jak to ostatně obecné guidelines doporučují pro všechny situace.

ViewState vs. fáze Init, aneb jak jsem se chytil

Na svém posledním ASP.NET kurzu jsem se báječně chytil na jednom primitivním demu na fungování ViewState. Krásně jsem předváděl, jak „ve fázi Init není ještě ViewState trackován, po fázi Init nastává LoadViewState a před tím se zapne jeho tracking – TrackViewState()“, nu což, vyrobil jsem si krásné demo a nestíhal jsem pak chvíli zírat.

(Základy fungování ViewState viz článek Co by měl každý vědět o ViewState.)

„Nefunkční“ demo

Vyrobil jsem primitivní stránku, s jedním Labelem a jedním tlačítkem.

<asp:Label ID="MyLabel" Text="Výchozí text" runat="server" />
<asp:Button ID="MyButton" Text="Postback" runat="server" />

a jak jsem se demonstrovat, jak přiřazení vlastnosti Text ve fázi Init nepřežije postback:

void Page_Init(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        MyLabel.Text = "Nová hodnota z fáze Init!";
    }
}

Uff, jaké bylo mé překvapení, když Label i po kliknutí na tlačítko Postback přežíval s hodnotou „Nová hodnota z fáze Init!“. Mnohým je hned jasné, kde jsem udělal chybu, ostatním přiblížím:

ViewState v životním cyklu stránek/controlů

Mé překvapení naštěstí netrvalo dlouho, rychle jsem si ověřil, kde přesně se volá v životním cyklu controlů TrackViewState() a rozuzlení bylo na světě:

  1. TrackViewState() není první operace po fázi Init, nýbrž poslední operace fáze Init samotné,
  2. fáze Init (jako jediná ze základních fází životního cyklu) probíhá zdola nahoru, procházením stromu control-tree do hloubky – fáze Init nadřazeného controlu je završena až po dokončení fází Init všech jeho child-controls.

Výsledek je tedy nasnadě, v obsluze události Page_Init jsme úplně na vrcholu control-tree a fáze Init jednotlivých controlů již proběhla. Jejich TrackViewState() již proběhl a ViewState controlů tak již sleduje změny jednotlivých hodnot. Nastavení MyLabel.Text ve fází Page_Init tedy již je operací, kdy změny ViewState labelu jsou sledovány, narozdíl od ViewState stránky (Page) samotné, kde ještě TrackViewState() neproběhl.

„Funkční“ demo

Demo jsem rychle opravil a vše krásně fungovalo, jak má:

void MyLabel_Init(object sender, EventArgs e)
{
    if (!Page.IsPostback)
    {
        this.Text = "Text nastavený ve fázi Init labelu, nepřežije Postback.";
    }
}

void Page_Init(object sender, EventArgs e)
{
    if (!IsPostback)
    {
        this.Title = "Title nastavený ve fázi Init page, nepřežije Postback.";
    }
}

Ve fázi Init samotného Labelu ještě jeho TrackViewState() neproběhl, tedy se Text nezachová.

Ve fázi Init stránky už proběhl TrackViewState() controlů, ale stránky samotné ještě ne, tedy třeba Title se nezachová.

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.