Sdílet prostřednictvím


Zpracování výjimek na úrovni knihoven BLL a DAL na stránce ASP.NET (VB)

od Scotta Mitchella

Stáhnout PDF

V tomto kurzu se dozvíme, jak zobrazit popisnou a informativní chybovou zprávu, pokud při vkládání, aktualizaci nebo odstranění webového ovládacího prvku ASP.NET dat dojde k výjimce.

Úvod

Práce s daty z webové aplikace ASP.NET pomocí vrstvené aplikační architektury zahrnuje následující tři obecné kroky:

  1. Určete, jakou metodu vrstvy obchodní logiky je potřeba vyvolat a jaké hodnoty parametrů se mají předat. Hodnoty parametrů můžou být pevně zakódované, programově přiřazené nebo vstupy zadané uživatelem.
  2. Vyvolejte metodu.
  3. Zpracovat výsledky. Při volání metody BLL, která vrací data, to může zahrnovat vytvoření vazby dat k datovému webovému ovládacímu prvku. U metod BLL, které upravují data, to může zahrnovat provedení určité akce na základě návratové hodnoty nebo řádné zpracování jakékoli výjimky, které vznikly v kroku 2.

Jak jsme viděli v předchozím kurzu, jak ObjectDataSource, tak datové webové ovládací prvky poskytují body rozšiřitelnosti pro kroky 1 a 3. GridView například aktivuje svou RowUpdating událost před přiřazením hodnot polí ke kolekci ObjectDataSource UpdateParameters ; její RowUpdated událost se vyvolá po dokončení operace ObjectDataSource.

Už jsme prozkoumali události, které se aktivují během kroku 1, a zjistili jsme, jak je můžete použít k přizpůsobení vstupních parametrů nebo zrušení operace. V tomto kurzu se podíváme na události, které se aktivují po dokončení operace. Díky těmto obslužným rutinám událostí po úrovni můžeme mimo jiné určit, jestli během operace došlo k výjimce, a zpracovat ji elegantně, zobrazit popisnou informativní chybovou zprávu na obrazovce místo výchozího nastavení na standardní stránce výjimky ASP.NET.

Pro ilustraci práce s těmito událostmi na úrovni vytvoříme stránku se seznamem produktů v upravitelném objektu GridView. Při aktualizaci produktu se na stránce ASP.NET zobrazí krátká zpráva nad objektem GridView s vysvětlením, že došlo k problému. Pojďme začít!

Krok 1: Vytvoření upravitelného objektu GridView produktů

V předchozím kurzu jsme vytvořili upravitelný Objekt GridView s pouze dvěma poli ProductName a UnitPrice. To vyžadovalo vytvoření dalšího přetížení metody ProductsBLL třídy UpdateProduct , které přijímalo pouze tři vstupní parametry (název produktu, jednotkovou cenu a ID) na rozdíl od parametru pro každé pole produktu. Pro účely tohoto kurzu si tuto techniku procvičeme znovu a vytvoříme upravitelný objekt GridView, který zobrazí název produktu, množství na jednotku, jednotkovou cenu a jednotky na skladě, ale umožňuje upravovat pouze název, jednotkovou cenu a jednotky na skladě.

Pro tento scénář budeme potřebovat další přetížení UpdateProduct metody, které přijímá čtyři parametry: název produktu, jednotková cena, jednotky na skladě a ID. Do třídy přidejte následující metodu ProductsBLL :

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
Public Function UpdateProduct _
    (ByVal productName As String, ByVal unitPrice As Nullable(Of Decimal), _
ByVal unitsInStock As Nullable(Of Short), ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = _
        Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

Po dokončení této metody jsme připraveni vytvořit stránku ASP.NET, která umožňuje upravit tato čtyři konkrétní pole produktu. ErrorHandling.aspx Otevřete stránku ve EditInsertDelete složce a přidejte objekt GridView na stránku prostřednictvím Návrháře. Napojte GridView na nový ObjectDataSource, pro mapování metody Select() na metodu třídy ProductsBLLGetProducts() a metodu Update() na nově vytvořené přetížení UpdateProduct.

Použijte přetížení metody UpdateProduct, které přijímá čtyři vstupní parametry

Obrázek 1: Použití UpdateProduct přetížení metody, která přijímá čtyři vstupní parametry (kliknutím na obrázek zobrazíte jeho plnou velikost)

Tím se vytvoří ObjectDataSource s UpdateParameters kolekcí se čtyřmi parametry a GridView s polem pro každé pole produktu. Deklarativní kód ObjectDataSource přiřadí hodnotu OldValuesParameterFormatString vlastnosti original_{0}, což způsobí výjimku, protože naše třída BLL neočekává předání vstupního parametru s názvem original_productID. Nezapomeňte toto nastavení úplně odebrat z deklarativní syntaxe (nebo ho nastavit na výchozí hodnotu). {0}

Dále zredukujte GridView tak, aby zahrnoval pouze ProductName, QuantityPerUnit, UnitPrice a UnitsInStock BoundFields. Můžete také použít veškeré formátování na úrovni pole, které považujete za nezbytné (například změnu HeaderText vlastností).

V předchozím kurzu jsme se podívali, jak formátovat UnitPrice BoundField jako měnu v režimu jen pro čtení i v režimu úprav. Uděláme to samé tady. Vzpomeňte si, že bylo nutné nastavit vlastnost DataFormatString pole BoundField na {0:c}, vlastnost HtmlEncode na false, a vlastnost ApplyFormatInEditMode na true, jak je znázorněno na obrázku 2.

Konfigurace hodnoty UnitPrice BoundField tak, aby se zobrazovala jako měna

Obrázek 2: Konfigurace UnitPrice pole BoundField tak, aby se zobrazilo jako měna (kliknutím zobrazíte obrázek s plnou velikostí)

Formátování UnitPrice jako měny v rozhraní pro úpravy vyžaduje vytvoření obslužné rutiny události GridView RowUpdating, která převede řetězec formátovaný měnou na hodnotu decimal. Vzpomeňte si, že obslužná rutina RowUpdating události z posledního kurzu také zkontrolovala, že uživatel zadal UnitPrice hodnotu. V tomto kurzu ale umožníme uživateli vynechat cenu.

Protected Sub GridView1_RowUpdating(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdateEventArgs) _
    Handles GridView1.RowUpdating
    If e.NewValues("UnitPrice") IsNot Nothing Then
        e.NewValues("UnitPrice") = _
            Decimal.Parse(e.NewValues("UnitPrice").ToString(), _
            System.Globalization.NumberStyles.Currency)
    End If

Naše GridView obsahuje QuantityPerUnit BoundField, ale tento BoundField by měl být pouze pro účely zobrazení a neměl by být upravitelný uživatelem. Chcete-li to zařídit, jednoduše nastavte vlastnost u BoundFields na hodnotu ReadOnly.

Udělejte pole QuantityPerUnit BoundField pouze pro čtení

Obrázek 3: Nastavení QuantityPerUnit Read-Only BoundField (kliknutím zobrazíte obrázek v plné velikosti)

Nakonec zaškrtněte políčko Povolit úpravy z kontextové nabídky GridView. Po dokončení těchto kroků by měl návrh stránky ErrorHandling.aspx vypadat podobně jako na obrázku 4.

Odebrat vše kromě potřebných boundfieldů a zaškrtnout políčko Povolit úpravy

Obrázek 4: Odebrání všech potřebných polí BoundField a zaškrtnutí políčka Povolit úpravy (kliknutím zobrazíte obrázek s plnou velikostí)

V tomto okamžiku máme seznam všech produktů ProductName, QuantityPerUnit, UnitPricea UnitsInStock polí, ale pouze ProductName, UnitPricea UnitsInStock pole lze upravit.

Uživatelé teď můžou snadno upravovat názvy, ceny a jednotky produktů v polích skladových zásob.

Obrázek 5: Uživatelé teď můžou snadno upravit názvy produktů, ceny a jednotky v polích skladových zásob (kliknutím zobrazíte obrázek s plnou velikostí).

Krok 2: Elegantní řešení výjimek DAL-Level

I když náš upravitelný GridView funguje skvěle, když uživatelé zadají právní hodnoty pro název, cenu a jednotky upraveného produktu na skladě, zadávání neplatných hodnot vede k výjimce. Například vynechání ProductName hodnoty způsobí vyvolání výjimky NoNullAllowedException, protože ProductName vlastnost ve ProductsRow třídě má svou AllowDBNull vlastnost nastavenu na false; pokud je databáze mimo provoz, SqlException bude vyvolán TableAdapterem při pokusu o připojení k databázi. Bez jakéhokoli zásahu tyto výjimky probublají z vrstvy přístupu k datům do vrstvy obchodní logiky, poté na stránku ASP.NET, a nakonec do ASP.NET runtime.

V závislosti na tom, jak je vaše webová aplikace nakonfigurovaná a jestli ji navštěvujete z localhost, neošetřená výjimka může vést k obecné stránce s chybou serveru, podrobné zprávě o chybě nebo uživatelsky přívětivé webové stránce. Další informace o tom, jak modul runtime ASP.NET reaguje na nezachycenou výjimku, najdete v tématu Zpracování chyb webové aplikace v ASP.NET a elementu customErrors .

Obrázek 6 ukazuje obrazovku, ke které došlo při pokusu o aktualizaci produktu bez zadání ProductName hodnoty. Toto je výchozí podrobná zpráva o chybách zobrazená při procházení localhost.

Vynechání názvu produktu zobrazí podrobnosti o výjimce.

Obrázek 6: Vynechání názvu produktu zobrazí podrobnosti o výjimce (kliknutím zobrazíte obrázek s plnou velikostí)

Ačkoli tyto podrobnosti o výjimce jsou užitečné při testování aplikace, předložení takové obrazovky koncovému uživateli v případě výjimky není ideální. Koncový uživatel pravděpodobně neví, co NoNullAllowedException je nebo proč byl způsoben. Lepším přístupem je prezentovat uživatele s uživatelsky přívětivější zprávou s vysvětlením, že při pokusu o aktualizaci produktu došlo k problémům.

Pokud při provádění operace dojde k výjimce, události na úrovni po zpracování v ObjectDataSource a datovém webovém ovládacím prvku poskytují prostředky k jeho zjištění a zabránění propagace výjimky do modulu runtime ASP.NET. V našem příkladu vytvoříme obslužnou rutinu události pro událost GridView RowUpdated , která určuje, jestli došlo k vyvolání výjimky, a pokud ano, zobrazí podrobnosti o výjimce v ovládacím prvku Label Web.

Začněte tím, že na stránku ASP.NET přidáte popisek, nastavíte jeho vlastnost na ID a vymažete jeho vlastnost ExceptionDetails. Chcete-li přilákat pozornost uživatele na tuto zprávu, nastavte její vlastnost CssClass na Warning, což je třída CSS, kterou jsme přidali v předchozím kurzu do souboru Styles.css. Vzpomeňte si, že tato třída CSS způsobí, že text popisku se zobrazí červeně, kurzívou, tučným písmem a navíc velkým písmem.

Přidejte webový ovládací prvek Popisek na stránku

Obrázek 7: Přidání webového ovládacího prvku Popisek na stránku (kliknutím zobrazíte obrázek v plné velikosti)

Vzhledem k tomu, že chceme, aby byl tento ovládací prvek Label Web viditelný pouze okamžitě poté, co došlo k výjimce, nastavte jeho Visible vlastnost na false v obslužné rutině Page_Load události:

Protected Sub Page_Load(sender As Object, e As System.EventArgs) Handles Me.Load
    ExceptionDetails.Visible = False
End Sub

S tímto kódem, při první návštěvě stránky a následných postbacích bude mít ovládací prvek ExceptionDetails svou vlastnost Visible nastavenu na false. V případě výjimky na úrovni DAL nebo BLL, kterou můžeme rozpoznat v obslužné rutině událostí GridView RowUpdated, nastavíme vlastnost ExceptionDetails ovládacího prvku Visible na true. Vzhledem k tomu, že obslužné rutiny událostí webového ovládacího prvku se spouštějí po Page_Load obslužné rutině události v životním cyklu stránky, bude štítek zobrazen. Nicméně, při dalším postbacku obslužná rutina události Page_Load vrátí vlastnost Visible zpět na false a ji znovu skryje ze zobrazení.

Poznámka:

Případně bychom mohli odebrat nutnost nastavovat vlastnost ovládacího prvku ExceptionDetailsVisible tím, že přiřadíme jeho vlastnost Page_Load hodnotě Visible v deklarativní syntaxi a zakážeme stav zobrazení (nastavením jeho vlastnosti false na EnableViewState). Tento alternativní přístup použijeme v budoucím kurzu.

Po přidání ovládacího prvku Label je dalším krokem vytvoření obslužné rutiny pro událost GridView RowUpdated. V Návrháři vyberte GridView, přejděte do okna Vlastnosti a klikněte na ikonu blesku se seznamem událostí GridView. Pro událost GridView RowUpdating by již měla existovat položka, protože jsme vytvořili obslužnou rutinu události pro tuto událost dříve v tomto kurzu. Vytvořte také obslužnou rutinu události pro danou RowUpdated událost.

Vytvoření obslužné rutiny pro událost RowUpdated objektu GridView

Obrázek 8: Vytvoření obslužné rutiny události pro událost GridView RowUpdated

Poznámka:

Obslužnou rutinu můžete také vytvořit prostřednictvím rozevíracích seznamů v horní části souboru třídy na pozadí. V rozevíracím seznamu na levé straně vyberte GridView a RowUpdated událost z rozevíracího seznamu na pravé straně.

Vytvoření této obslužné rutiny události přidá následující kód do kódové části stránky ASP.NET.

Protected Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
End Sub

Druhý vstupní parametr obslužné rutiny události je objekt typu GridViewUpdatedEventArgs, který má tři vlastnosti zájmu pro zpracování výjimek:

  • Exception odkaz na vyvolanou výjimku; Pokud nebyla vyvolána žádná výjimka, bude tato vlastnost mít hodnotu null
  • ExceptionHandled Logická hodnota, která označuje, zda byla výjimka zpracována v RowUpdated obslužné rutině události; pokud false (výchozí), je výjimka znovu vyvolána a propaguje se v prostředí ASP.NET runtime.
  • KeepInEditMode pokud je nastaven na true upravený řádek GridView zůstane v režimu úprav; pokud false (výchozí), řádek GridView se vrátí zpět do režimu jen pro čtení.

Náš kód by pak měl zkontrolovat, zda Exception není null, což znamená, že při provádění operace byla vyvolána výjimka. V takovém případě chceme:

  • Zobrazení uživatelsky přívětivé zprávy v popisku ExceptionDetails
  • Označuje, že výjimka byla zpracována.
  • Zachování řádku GridView v režimu úprav

Tento následující kód dosahuje těchto cílů:

Protected Sub GridView1_RowUpdated(ByVal sender As Object, _
    ByVal e As System.Web.UI.WebControls.GridViewUpdatedEventArgs) _
    Handles GridView1.RowUpdated
    If e.Exception IsNot Nothing Then
        ExceptionDetails.Visible = True
        ExceptionDetails.Text = "There was a problem updating the product. "
        If e.Exception.InnerException IsNot Nothing Then
            Dim inner As Exception = e.Exception.InnerException
            If TypeOf inner Is System.Data.Common.DbException Then
                ExceptionDetails.Text &= _
                "Our database is currently experiencing problems." & _
                "Please try again later."
            ElseIf TypeOf inner _
             Is System.Data.NoNullAllowedException Then
                ExceptionDetails.Text += _
                    "There are one or more required fields that are missing."
            ElseIf TypeOf inner Is ArgumentException Then
                Dim paramName As String = CType(inner, ArgumentException).ParamName
                ExceptionDetails.Text &= _
                    String.Concat("The ", paramName, " value is illegal.")
            ElseIf TypeOf inner Is ApplicationException Then
                ExceptionDetails.Text += inner.Message
            End If
        End If
        e.ExceptionHandled = True
        e.KeepInEditMode = True
    End If
End Sub

Tato obslužná rutina události začíná kontrolou, zda e.Exception je null. Pokud ne, ExceptionDetails vlastnost Visible Label je nastavena na true a Text na "Došlo k problému s aktualizací produktu". Podrobnosti o skutečné výjimce, která byla vyvolána, se nacházejí v objektu e.Exception vlastnosti InnerException. Tato vnitřní výjimka je zkoumána a pokud se jedná o konkrétní typ, k vlastnosti ExceptionDetails Labelu Text se připojí další užitečná zpráva. Nakonec jsou vlastnosti ExceptionHandled a KeepInEditMode obě nastaveny na true hodnotu.

Obrázek 9 ukazuje snímek obrazovky této stránky při vynechání názvu produktu; Obrázek 10 ukazuje výsledky při zadávání neplatné UnitPrice hodnoty (-50).

Název ProductName BoundField musí obsahovat hodnotu.

Obrázek 9: BoundField ProductName musí obsahovat hodnotu (kliknutím zobrazíte obrázek v plné velikosti).

Záporné hodnoty jednotkové ceny nejsou povoleny.

Obrázek 10: Záporné UnitPrice hodnoty nejsou povoleny (kliknutím zobrazíte obrázek s plnou velikostí)

Když je vlastnost e.ExceptionHandled nastavena na true, obslužná rutina události RowUpdated uvádí, že výjimku zpracovala. Proto se výjimka nerozšíře až do modulu runtime ASP.NET.

Poznámka:

Obrázky 9 a 10 ukazují elegantní způsob zpracování výjimek vyvolaných z důvodu neplatného uživatelského vstupu. V ideálním případě by se takový neplatný vstup vůbec neměl dostat na vrstvu obchodní logiky, protože stránka ASP.NET by měla zajistit, že uživatelské vstupy jsou platné předtím, než vyvolá metodu ProductsBLL třídy UpdateProduct. V dalším kurzu se dozvíte, jak přidat ověřovací ovládací prvky do rozhraní pro úpravy a vkládání, aby data odeslaná do vrstvy obchodní logiky odpovídala obchodním pravidlům. Ověřovací ovládací prvky nejen brání vyvolání UpdateProduct metody, dokud nebudou data zadaná uživatelem platná, ale také poskytují informativnější uživatelské prostředí pro identifikaci problémů se zadáváním dat.

Krok 3: Řádné zpracování výjimek BLL-Level

Při vkládání, aktualizaci nebo odstraňování dat může vrstva přístupu k datům vyvolat výjimku v případě chyby související s daty. Databáze může být offline, požadovaný sloupec tabulky databáze pravděpodobně neměl zadanou hodnotu nebo mohlo dojít k porušení omezení na úrovni tabulky. Kromě výhradně výjimek souvisejících s daty může vrstva obchodní logiky používat výjimky k označení, kdy byla porušena obchodní pravidla. V kurzu Vytvoření vrstvy obchodní logiky jsme například přidali kontrolu obchodního pravidla do původního UpdateProduct přetížení. Konkrétně pokud uživatel označoval výrobek jako ukončený, vyžadovali jsme, aby produkt nebyl jediným produktem, který poskytl jeho dodavatel. Pokud byla tato podmínka porušena, byl ApplicationException vyvolán.

Pro přetížení vytvořené v tomto kurzu přidáme obchodní pravidlo, které zakazuje nastavit pole UpdateProduct na novou hodnotu, která je větší než dvojnásobek původní hodnoty UnitPrice. Chcete-li toho dosáhnout, upravte UpdateProduct přetížení tak, aby provedlo tuto kontrolu a vyvolalo ApplicationException, pokud je pravidlo porušeno. Aktualizovaná metoda následuje:

<System.ComponentModel.DataObjectMethodAttribute _
    (System.ComponentModel.DataObjectMethodType.Update, True)> _
    Public Function UpdateProduct(ByVal productName As String, _
    ByVal unitPrice As Nullable(Of Decimal), ByVal unitsInStock As Nullable(Of Short), _
    ByVal productID As Integer) As Boolean
    Dim products As Northwind.ProductsDataTable = Adapter.GetProductByProductID(productID)
    If products.Count = 0 Then
        Return False
    End If
    Dim product As Northwind.ProductsRow = products(0)
    If unitPrice.HasValue AndAlso Not product.IsUnitPriceNull() Then
        If unitPrice > product.UnitPrice * 2 Then
            Throw New ApplicationException( _
                "When updating a product price," & _
                " the new price cannot exceed twice the original price.")
        End If
    End If
    product.ProductName = productName
    If Not unitPrice.HasValue Then
        product.SetUnitPriceNull()
    Else
        product.UnitPrice = unitPrice.Value
    End If
    If Not unitsInStock.HasValue Then
        product.SetUnitsInStockNull()
    Else
        product.UnitsInStock = unitsInStock.Value
    End If
    Dim rowsAffected As Integer = Adapter.Update(product)
    Return rowsAffected = 1
End Function

Při této změně způsobí vyvolání jakékoli aktualizace cen, která je více než dvojnásobek stávající ceny ApplicationException . Stejně jako výjimku vyvolanou z DAL lze výjimku vyvolanou BLL ApplicationException zjistit a zpracovat v obslužné rutině události RowUpdated GridView. Ve skutečnosti kód obslužné rutiny události RowUpdated, jak je zapsán, správně rozpozná tuto výjimku a zobrazí hodnotu vlastnosti ApplicationExceptionMessage. Obrázek 11 ukazuje snímek obrazovky, když se uživatel pokusí aktualizovat cenu Chai na 50,00 USD, což je více než dvojnásobek aktuální ceny 19,95 USD.

Obchodní pravidla nedovolují zvýšení ceny, které více než zdvojnásobí cenu produktu

Obrázek 11: Obchodní pravidla neumožňují zvýšení cen, které by více než zdvojnásobily cenu produktu (kliknutím zobrazíte obrázek plné velikosti)

Poznámka:

V ideálním případě by naše pravidla obchodní logiky měla být refaktorována z přetížení metody UpdateProduct do společné metody. Toto je pro čtenáře ponecháno jako cvičení.

Shrnutí

Při vkládání, aktualizaci a odstraňování operací webový datový ovládací prvek a ObjectDataSource zapojují události na úrovni před a po, které ohraničují skutečnou operaci. Jak jsme viděli v tomto kurzu a v předchozím, když pracujeme s editovatelným GridView, událost GridView RowUpdating se aktivuje, následovaná událostí ObjectDataSource Updating, a v tomto bodě se provádí aktualizační příkaz na základní objekt ObjectDataSource. Po dokončení operace se aktivuje událost ObjectDataSource Updated následovaná událostí GridView RowUpdated .

Můžeme vytvořit obslužné rutiny pro události před úrovní, abychom přizpůsobili vstupní parametry, nebo pro události po úrovni, abychom mohli zkontrolovat výsledky operace a reagovat na ně. Obslužné rutiny událostí na úrovni se nejčastěji používají ke zjištění, jestli během operace došlo k výjimce. V případě výjimky mohou tyto obslužné rutiny událostí na postúrovni samostatně a volitelně zpracovat výjimku. V tomto kurzu jsme viděli, jak takovou výjimku zpracovat zobrazením popisné chybové zprávy.

V dalším kurzu se dozvíte, jak zmenšit pravděpodobnost výjimek vyplývajících z problémů s formátováním dat (například zadáním záporné UnitPricehodnoty). Konkrétně se podíváme na to, jak přidat ověřovací ovládací prvky do rozhraní pro úpravy a vkládání.

Šťastné programování!

O autorovi

Scott Mitchell, autor sedmi knih ASP/ASP.NET a zakladatel 4GuysFromRolla.com, pracuje s webovými technologiemi Microsoftu od roku 1998. Scott pracuje jako nezávislý konzultant, trenér a spisovatel. Jeho nejnovější kniha je Sams: Nauč se ASP.NET 2.0 za 24 hodin. Může být dosažitelný na mitchell@4GuysFromRolla.comadrese .

Zvláštní díky

Tato série kurzů byla zkontrolována mnoha užitečnými recenzenty. Vedoucí recenzent pro tento kurz byl Liz Shulok. Chcete si projít nadcházející články MSDN? Pokud ano, napište mi zprávu na mitchell@4GuysFromRolla.com.