Dela via


Hantera BLL- och DAL-Level-undantag på en ASP.NET sida (VB)

av Scott Mitchell

Ladda ned PDF

I den här självstudien får vi se hur du visar ett användarvänligt, informativt felmeddelande om ett undantag skulle inträffa under en infognings-, uppdaterings- eller borttagningsåtgärd för en ASP.NET datawebbkontroll.

Inledning

Att arbeta med data från en ASP.NET webbapp med hjälp av en nivåindelad programarkitektur omfattar följande tre allmänna steg:

  1. Ta reda på vilken metod i affärslogiklagret som måste anropas och vilka parametervärden som ska skickas. Parametervärdena kan vara hårdkodade, programmatiskt tilldelade eller indata som anges av användaren.
  2. Anropa metoden.
  3. Bearbeta resultaten. När du anropar en BLL-metod som returnerar data kan det innebära att data binds till en datawebbkontroll. För BLL-metoder som ändrar data kan detta omfatta att utföra en åtgärd baserat på ett returvärde eller hantera eventuella undantag som uppstod i steg 2.

Som vi såg i den föregående självstudien ger både ObjectDataSource- och datawebbkontrollerna utökningspunkter för steg 1 och 3. GridView utlöser till exempel sin RowUpdating händelse innan dess fältvärden tilldelas UpdateParameters dess ObjectDataSource-samling. Händelsen RowUpdated utlöses när ObjectDataSource har slutfört åtgärden.

Vi har redan undersökt de händelser som utlöses under steg 1 och har sett hur de kan användas för att anpassa indataparametrarna eller avbryta åtgärden. I den här handledningen ska vi uppmärksamma de händelser som inträffar när åtgärden har slutförts. Med dessa händelsehanterare på efternivå kan vi bland annat avgöra om ett undantag inträffade under åtgärden och hantera det på ett korrekt sätt, och visa ett vänligt, informativt felmeddelande på skärmen i stället för att som standard använda standardsidan för ASP.NET undantag.

För att illustrera hur du arbetar med dessa händelser på efternivå ska vi skapa en sida som visar produkterna i ett redigerbart GridView. När en produkt uppdateras, och ett undantag uppstår, kommer vår ASP.NET sida att visa ett kort meddelande ovanför GridView som förklarar att ett problem har uppstått. Nu ska vi komma igång!

Steg 1: Skapa en redigerbar GridView av produkter

I den föregående handledningen skapade vi ett redigerbart GridView med bara två fält, ProductName och UnitPrice. Detta krävde att du skapade ytterligare en överlagring för ProductsBLL klassens UpdateProduct -metod, en som endast accepterade tre indataparametrar (produktens namn, enhetspris och ID) i stället för en parameter för varje produktfält. I den här självstudien ska vi öva på den här tekniken igen och skapa en redigerbar GridView som visar produktens namn, kvantitet per enhet, enhetspris och enheter i lager, men som bara tillåter att namn, enhetspris och enheter i lager redigeras.

För att hantera det här scenariot behöver vi ytterligare en överlagring av UpdateProduct metoden, en som accepterar fyra parametrar: produktens namn, enhetspris, enheter i lager och ID. Lägg till följande metod i klassen 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

När den här metoden är klar är vi redo att skapa den ASP.NET sida som gör det möjligt att redigera dessa fyra specifika produktfält. Öppna sidan ErrorHandling.aspx i EditInsertDelete mappen och lägg till en GridView på sidan via designern. Binda GridView till en ny ObjectDataSource, koppla Select()-metoden till ProductsBLL-klassens GetProducts()-metod, och Update()-metoden till den just skapade UpdateProduct-överlagringen.

Använd UpdateProduct-metodens överbelastning som accepterar fyra indataparametrar

Bild 1: Använd metodöverbelastningen UpdateProduct som accepterar fyra indataparametrar (klicka om du vill visa en bild i full storlek)

Då skapas en ObjectDataSource med en UpdateParameters samling med fyra parametrar och en GridView med ett fält för vart och ett av produktfälten. ObjectDataSources deklarativa markering tilldelar OldValuesParameterFormatString egenskapen värdet original_{0}, vilket orsakar ett undantag eftersom vår BLL-klass inte förväntar sig att en indataparameter med namnet original_productID skickas in. Glöm inte att ta bort den här inställningen helt från den deklarativa syntaxen (eller ställ in den på standardvärdet , {0}).

Skala ner sedan Rutnätsvyn så att den endast innehåller ProductName, QuantityPerUnit, UnitPrice och UnitsInStock BoundFields. Du kan också använda valfri formatering på fältnivå som du anser vara nödvändig (till exempel att ändra HeaderText egenskaperna).

I den föregående självstudien tittade vi på hur du formaterar UnitPrice BoundField som en valuta både i skrivskyddat läge och redigeringsläge. Vi gör samma sak här. Kom ihåg att detta krävde inställning av BoundFields DataFormatString egenskap till {0:c}, dess HtmlEncode egenskap till false, och dess ApplyFormatInEditMode till true, som visas i bild 2.

Konfigurera UnitPrice BoundField så att det visas som en valuta

Bild 2: Konfigurera BoundField så att det UnitPrice visas som en valuta (klicka om du vill visa en bild i full storlek)

För att formatera UnitPrice som en valuta i redigeringsgränssnittet RowUpdating måste du skapa en händelsehanterare för GridView-händelsen som parsar den valutaformaterade strängen till ett decimal värde. Kom ihåg att RowUpdating händelsehanteraren från den senaste handledningen, också kontrollerade för att säkerställa att användaren angav ett UnitPrice värde. I den här självstudien ska vi dock tillåta att användaren utelämnar priset.

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

Vår GridView innehåller ett QuantityPerUnit BoundField, men det här BoundField bör endast vara i visningssyfte och bör inte kunna redigeras av användaren. Om du vill ordna detta anger du helt enkelt egenskapen BoundFields ReadOnly till true.

Gör QuantityPerUnit BoundField skrivskyddat

Bild 3: Gör QuantityPerUnit BoundField Read-Only (Klicka om du vill visa en bild i full storlek)

Markera slutligen kryssrutan Aktivera redigering från GridViews smarta tagg. När du har slutfört de ErrorHandling.aspx här stegen bör sidans designer se ut ungefär som i bild 4.

Ta bort alla utom nödvändiga boundfields och markera kryssrutan Aktivera redigering

Bild 4: Ta bort alla utom nödvändiga boundfields och markera kryssrutan Aktivera redigering (Klicka om du vill visa en bild i full storlek)

Nu har vi en lista över alla produkter, ProductName, QuantityPerUnit, UnitPriceoch UnitsInStock fält, men endast fälten ProductName, UnitPriceoch UnitsInStock kan redigeras.

Användare kan nu enkelt redigera produkters namn, priser och enheter i lagerfält

Bild 5: Användare kan nu enkelt redigera produkters namn, priser och enheter i lagerfält (Klicka om du vill visa en bild i full storlek)

Steg 2: Hantera DAL-Level undantag på ett korrekt sätt

Även om vår redigerbara GridView fungerar utmärkt när användare anger giltiga värden för namn, pris och enheter i lager för den redigerade produkten, leder inmatning av ogiltiga värden till ett undantag. Om du till exempel utelämnar ProductName värdet genereras en NoNullAllowedException eftersom ProductName egenskapen i ProductsRow klassen har egenskapen AllowDBNull inställd på false. Om databasen är nere genereras en SqlException av TableAdapter när du försöker ansluta till databasen. Utan att vidta några åtgärder bubblar dessa undantag upp från dataåtkomstskiktet till affärslogiklagret, sedan till sidan ASP.NET och slutligen till ASP.NET-körningen.

Beroende på hur webbprogrammet har konfigurerats och om du besöker programmet från localhostkan ett ohanterat undantag resultera i antingen en allmän serverfelsida, en detaljerad felrapport eller en användarvänlig webbsida. Se Felhantering av webbapplikationer i ASP.NET och customErrors-elementet för mer information om hur ASP.NET-runtime svarar på ett ej fångat undantag.

Bild 6 visar skärmen som påträffades vid försök att uppdatera en produkt utan att ProductName ange värdet. Det här är standardrapporten för detaljerade fel som visas när du kommer via localhost.

Om du utelämnar produktens namn visas undantagsinformation

Bild 6: Om du utelämnar produktens namn visas undantagsinformation (klicka om du vill visa en bild i full storlek)

Även om sådan undantagsinformation är användbar när du testar ett program, är det mindre än idealiskt att presentera en slutanvändare med en sådan skärm inför ett undantag. En slutanvändare vet förmodligen inte vad en NoNullAllowedException är eller varför den orsakades. En bättre metod är att ge användaren ett mer användarvänligt meddelande som förklarar att det uppstod problem med att försöka uppdatera produkten.

Om ett undantag inträffar när åtgärden utförs, ger händelserna på postnivå i både ObjectDataSource och datakontrollen ett sätt att identifiera det och avbryta undantaget från att bubbla upp till ASP.NET-körningen. I vårt exempel ska vi skapa en händelsehanterare för GridView-händelsen som avgör om ett undantag har utlösts RowUpdated och i så fall visar undantagsinformationen i en etikettwebbkontroll.

Börja med att lägga till en etikett på sidan ASP.NET, ange dess ID egenskap till ExceptionDetails och rensa dess Text egenskap. För att dra användarens öga till det här meddelandet anger du dess CssClass egenskapen till Warning, vilket är en CSS-klass som vi lade till i Styles.css-filen i föregående handledning. Kom ihåg att den här CSS-klassen gör att etikettens text visas i ett rött, kursivt, fetstilt, extra stort teckensnitt.

Lägg till en etikettwebbkontroll på sidan

Bild 7: Lägg till en etikettwebbkontroll på sidan (Klicka om du vill visa en bild i full storlek)

Eftersom vi vill att den här etikettwebbkontrollen bara ska visas omedelbart efter att ett undantag har inträffat anger du dess Visible egenskap till false i Page_Load händelsehanteraren:

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

Med den här koden, vid det första sidbesöket och efterföljande postbacks, kommer ExceptionDetails-kontrollen att ha sin Visible-egenskap inställd på false. Inför ett undantag på DAL- eller BLL-nivå, som vi kan identifiera i GridViews RowUpdated händelsehanterare, anger ExceptionDetails vi kontrollens Visible egenskap till true. Eftersom händelsehanterare för webbkontroll inträffar efter Page_Load händelsehanteraren i sidlivscykeln visas etiketten. Vid nästa postback kommer Page_Load händelsehanteraren dock att återställa egenskapen Visible till false och dölja den från vyn igen.

Anmärkning

Alternativt kan vi ta bort behovet av att ange ExceptionDetails kontrollens Visible egenskap genom Page_Load att tilldela dess Visible egenskap false i den deklarativa syntaxen och inaktivera dess vytillstånd (ange dess EnableViewState egenskap till false). Vi kommer att använda det här alternativa tillvägagångssättet i en kommande handledning.

När etikettkontrollen har lagts till, är vårt nästa steg att skapa händelsehanteraren för GridViews RowUpdated-händelse. Välj GridView i designern, gå till fönstret Egenskaper och klicka på blixtikonen och visa gridview-händelserna. Det bör redan finnas en post där för GridView-händelsen RowUpdating , eftersom vi skapade en händelsehanterare för den här händelsen tidigare i den här självstudien. Skapa även en händelsehanterare för RowUpdated händelsen.

Skapa en händelsehanterare för GridViews RowUpdated-händelse

Bild 8: Skapa en händelsehanterare för GridView-händelsen RowUpdated

Anmärkning

Du kan också skapa händelsehanteraren via listrutorna överst i klassfilen code-behind. Välj GridView i listrutan till vänster och RowUpdated-händelsen från den till höger.

När du skapar den här händelsehanteraren läggs följande kod till i ASP.NET-sidans kod bakom-klass:

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

Den här händelsehanterarens andra indataparameter är ett objekt av typen GridViewUpdatedEventArgs, som har tre egenskaper av intresse för att hantera undantag:

  • Exception En hänvisning till undantaget som utlöses. om inget undantag har genererats har den här egenskapen värdet null
  • ExceptionHandled ett booleskt värde som anger om undantaget hanterades i RowUpdated händelsehanteraren eller inte. Om false (standardvärdet) genereras undantaget på nytt och bubblar upp till ASP.NET-runtime
  • KeepInEditMode om den är inställd till true, förblir den redigerade GridView-raden i redigeringsläge; om false (standardvärdet) återgår GridView-raden till skrivskyddat läge

Vår kod bör sedan kontrollera om Exception är inte null, vilket innebär att ett undantag uppstod när åtgärden utfördes. I så fall vill vi:

  • Visa ett användarvänligt meddelande i etiketten ExceptionDetails
  • Ange att undantaget hanterades
  • Behåll GridView-raden i redigeringsläge

Följande kod åstadkommer följande må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

Den här händelsehanteraren börjar med att kontrollera om e.Exception är null. Om det inte är ExceptionDetails, ställs Etikettens Visible-egenskap in på true och dess Text-egenskap till "Det uppstod ett problem med att uppdatera produkten." Information om det faktiska undantaget som utlöstes finns i e.Exception-objektets InnerException-egenskap. Det här inre undantaget granskas och om det är av en viss typ läggs ytterligare ett användbart meddelande till i ExceptionDetails egenskapen Etikett Text . Slutligen är både egenskaperna ExceptionHandled och KeepInEditMode inställda på true.

Bild 9 visar en skärmbild av den här sidan när du utelämnar namnet på produkten. Bild 10 visar resultatet när du anger ett ogiltigt UnitPrice värde (-50).

ProductName BoundField måste innehålla ett värde

Bild 9: ProductName BoundField måste innehålla ett värde (klicka om du vill visa en bild i full storlek)

Negativa UnitPrice-värden tillåts inte

Bild 10: Negativa UnitPrice värden tillåts inte (Klicka om du vill visa en bild i full storlek)

Genom att ange e.ExceptionHandled egenskapen till trueRowUpdated har händelsehanteraren angett att den har hanterat undantaget. Undantaget kommer därför inte att propageras till ASP.NET körmiljö.

Anmärkning

Figurerna 9 och 10 visar ett smidigt sätt att hantera undantag som uppstår på grund av ogiltiga användarindata. Men idealt ska sådana ogiltiga indata aldrig nå affärslogiklagret överhuvudtaget, eftersom ASP.NET-sidan ska säkerställa att användarens indata är giltiga innan klassens ProductsBLL-metod UpdateProduct anropas. I nästa självstudie får vi se hur du lägger till verifieringskontroller i redigerings- och infogningsgränssnitten för att säkerställa att data som skickas till affärslogiklagret överensstämmer med affärsreglerna. Valideringskontrollerna förhindrar inte bara att metoden anropas UpdateProduct förrän data från användaren är giltiga, utan ger också en mer informativ användarupplevelse för att identifiera problem med datainmatning.

Steg 3: Hantera BLL-Level undantag på ett korrekt sätt

Vid infogning, uppdatering eller borttagning av data kan dataåtkomstskiktet utlösa ett undantag inför ett datarelaterat fel. Databasen kan vara offline, en obligatorisk databastabellkolumn kanske inte har angett något värde eller så kan en begränsning på tabellnivå ha brutits. Förutom strikt datarelaterade undantag kan Business Logic Layer använda undantag för att ange när affärsregler har brutits. I självstudien Skapa ett affärslogiklager har vi till exempel lagt till en kontroll av affärsregler i den ursprungliga UpdateProduct överbelastningen. Mer specifikt, om användaren markerade en produkt som upphörd, krävde vi att produkten inte var den enda som tillhandahålls av leverantören. Om det här villkoret överträddes, utlösts en ApplicationException.

För den UpdateProduct överbelastning som skapas i den här handledningen ska vi lägga till en affärsregel som förbjuder UnitPrice att fältet anges till ett nytt värde som är mer än dubbelt så stort som det ursprungliga UnitPrice värdet. För att åstadkomma detta justerar du överbelastningen UpdateProduct så att den utför den här kontrollen och genererar en ApplicationException om regeln överträds. Den uppdaterade metoden följer:

<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

Med den här ändringen kommer alla prisuppdateringar som är mer än dubbelt så höga som det befintliga priset att utlösa en ApplicationException. Precis som undantaget från DAL kan detta BLL-utlösta ApplicationException identifieras och hanteras i GridViewens RowUpdated-händelsehanterare. Faktum är att RowUpdated händelsehanterarens kod, som den är skriven, korrekt identifierar undantaget och visar ApplicationExceptionegenskapsvärdet Message . Bild 11 visar en skärmdump när en användare försöker uppdatera priset på Chai till $ 50.00, vilket är mer än dubbelt så mycket som det nuvarande priset på $ 19.95.

Affärsreglerna tillåter inte prisökningar som mer än fördubblar en produkts pris

Bild 11: Affärsreglerna tillåter inte prisökningar som mer än fördubblar en produkts pris (klicka om du vill visa en bild i full storlek)

Anmärkning

Helst skulle våra affärslogikregler omstruktureras från UpdateProduct metodöverlagringarna och till en gemensam metod. Detta lämnas som en övning för läsaren.

Sammanfattning

Under infogande, uppdatering och borttagning av åtgärder är både datawebbkontrollen och ObjectDataSource inblandade och utlöser händelser på pre- och postnivå som omsluter själva åtgärden. Som vi såg i den här självstudien och den föregående, när du arbetar med ett redigerbart GridView utlöses GridView-RowUpdating-händelsen, följt av ObjectDataSource-Updating-händelsen, vid vilken punkt uppdateringskommandot körs på ObjectDataSources underliggande objekt. När åtgärden har slutförts utlöses ObjectDataSources Updated händelse följt av GridView-händelsen RowUpdated .

Vi kan skapa händelsehanterare för händelser på förnivå för att anpassa indataparametrarna eller för händelser på efternivå för att inspektera och svara på åtgärdens resultat. Händelsehanterare på efternivå används oftast för att identifiera om ett undantag inträffade under åtgärden. Vid ett undantag kan dessa händelsehanterare på postnivå hantera undantaget på egen hand. I den här guiden såg vi hur man hanterar ett sådant undantag genom att visa ett vänligt felmeddelande.

I nästa självstudie får vi se hur du minskar sannolikheten för undantag som uppstår vid problem med dataformatering (till exempel att ange ett negativt UnitPrice). Mer specifikt ska vi titta på hur du lägger till verifieringskontroller i redigerings- och infogningsgränssnitten.

Lycka till med programmerandet!

Om författaren

Scott Mitchell, författare till sju ASP/ASP.NET-böcker och grundare av 4GuysFromRolla.com, har arbetat med Microsofts webbtekniker sedan 1998. Scott arbetar som oberoende konsult, tränare och författare. Hans senaste bok är Sams Teach Yourself ASP.NET 2.0 på 24 timmar. Han kan nås på mitchell@4GuysFromRolla.com.

Särskilt tack till

Den här självstudieserien granskades av många användbara granskare. Ansvarig granskare för den här handledningen var Liz Shulok. Vill du granska mina kommande MSDN-artiklar? Om så är fallet, hör av dig på mitchell@4GuysFromRolla.com.