Gestione delle eccezioni a livello BLL e DAL in una pagina ASP.NET (C#)
In questa esercitazione verrà illustrato come visualizzare un messaggio di errore descrittivo e informativo deve verificarsi un'eccezione durante un'operazione di inserimento, aggiornamento o eliminazione di un controllo Web di ASP.NET dati.
Introduzione
L'uso dei dati da un'applicazione Web ASP.NET tramite un'architettura di applicazioni a livelli prevede i tre passaggi generali seguenti:
- Determinare quale metodo del livello di logica di business deve essere richiamato e quali valori di parametro passare. I valori dei parametri possono essere codificati, assegnati a livello di codice a livello di codice o input immessi dall'utente.
- Richiamare il metodo.
- Elaborare i risultati. Quando si chiama un metodo BLL che restituisce i dati, ciò può comportare l'associazione dei dati a un controllo Web dati. Per i metodi BLL che modificano i dati, questa operazione può includere l'esecuzione di alcune azioni in base a un valore restituito o alla gestione di qualsiasi eccezione generata nel passaggio 2.
Come illustrato nell'esercitazione precedente, sia i controlli ObjectDataSource che i controlli Web dati forniscono punti di estendibilità per i passaggi 1 e 3. GridView, ad esempio, genera l'evento RowUpdating
prima di assegnare i relativi valori di campo all'insieme ObjectDataSource. RowUpdated
L'evento viene generato dopo che ObjectDataSource UpdateParameters
ha completato l'operazione.
Sono già stati esaminati gli eventi che vengono attivati durante il passaggio 1 e hanno visto come possono essere usati per personalizzare i parametri di input o annullare l'operazione. In questa esercitazione si esaminerà l'attenzione sugli eventi che vengono attivati dopo il completamento dell'operazione. Con questi gestori eventi a livello post-livello è possibile, tra le altre cose, determinare se si è verificata un'eccezione durante l'operazione e gestirla in modo normale, visualizzando un messaggio di errore descrittivo e informativo sullo schermo anziché per impostazione predefinita alla pagina di eccezione ASP.NET standard.
Per illustrare l'uso di questi eventi di post-livello, verrà creata una pagina che elenca i prodotti in una griglia modificabile. Quando si aggiorna un prodotto, se viene generata un'eccezione, la pagina ASP.NET visualizzerà un breve messaggio sopra GridView che spiega che si è verificato un problema. È possibile iniziare subito.
Passaggio 1: Creazione di una griglia modificabile di prodotti
Nell'esercitazione precedente è stato creato un controllo GridView modificabile con solo due campi ProductName
e UnitPrice
. Ciò richiedeva la creazione di un overload aggiuntivo per il ProductsBLL
metodo della UpdateProduct
classe, uno che accettava solo tre parametri di input (il nome del prodotto, il prezzo unitario e l'ID) anziché un parametro per ogni campo del prodotto. Per questa esercitazione, si pratica di nuovo questa tecnica, creando un gridView modificabile che visualizza il nome del prodotto, la quantità per unità, il prezzo unitario e le unità in magazzino, ma consente di modificare solo il nome, il prezzo unitario e le unità in magazzino.
Per supportare questo scenario, sarà necessario un altro overload del UpdateProduct
metodo, uno che accetta quattro parametri: il nome del prodotto, il prezzo unitario, le unità in magazzino e l'ID. Aggiungere il metodo seguente alla classe ProductsBLL
:
[System.ComponentModel.DataObjectMethodAttribute(
System.ComponentModel.DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull();
else product.UnitPrice = unitPrice.Value;
if (unitsInStock == null) product.SetUnitsInStockNull();
else product.UnitsInStock = unitsInStock.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Con questo metodo è possibile creare la pagina ASP.NET che consente di modificare questi quattro campi di prodotto specifici. Aprire la ErrorHandling.aspx
pagina nella EditInsertDelete
cartella e aggiungere GridView alla pagina tramite il Designer. Associare GridView a un nuovo OggettoDataSource, mapping Select()
del metodo al ProductsBLL
metodo della GetProducts()
classe e al Update()
metodo all'overload UpdateProduct
appena creato.
Figura 1: Usare l'overload del metodo che accetta quattro parametri di input (fare clic per visualizzare l'immagineUpdateProduct
full-size)
Verrà creato un oggetto ObjectDataSource con una UpdateParameters
raccolta con quattro parametri e un oggetto GridView con un campo per ognuno dei campi del prodotto. Il markup dichiarativo di ObjectDataSource assegna alla proprietà il valore original_{0}
, che causerà un'eccezione perché la OldValuesParameterFormatString
classe BLL non prevede che venga passato un parametro di input denominatooriginal_productID
. Non dimenticare di rimuovere completamente questa impostazione dalla sintassi dichiarativa (o impostarla sul valore predefinito, {0}
).
Successivamente, pare giù GridView per includere solo , ProductName
, QuantityPerUnit
UnitPrice
e UnitsInStock
BoundFields. Inoltre, è possibile applicare qualsiasi formattazione a livello di campo che si ritiene necessaria (ad esempio la modifica delle HeaderText
proprietà).
Nell'esercitazione UnitPrice
precedente è stato illustrato come formattare BoundField come valuta sia in modalità di sola lettura che in modalità di modifica. Facciamo la stessa cosa qui. Tenere presente che questa impostazione obbligatoria della DataFormatString
proprietà BoundField su , la relativa HtmlEncode
proprietà {0:c}
su false
e il relativo ApplyFormatInEditMode
su true
, come illustrato nella figura 2.
Figura 2: Configurare BoundField per visualizzare come valuta (fare clic per visualizzare l'immagineUnitPrice
a dimensioni complete)
La formattazione UnitPrice
di come valuta nell'interfaccia di modifica richiede la creazione di un gestore eventi per l'evento RowUpdating
gridView che analizza la stringa formattata in valuta in un decimal
valore. Tenere presente che il RowUpdating
gestore eventi dell'ultima esercitazione ha controllato anche per assicurarsi che l'utente ha fornito un UnitPrice
valore. Tuttavia, per questa esercitazione è possibile consentire all'utente di omettere il prezzo.
protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
if (e.NewValues["UnitPrice"] != null)
e.NewValues["UnitPrice"] =decimal.Parse(e.NewValues["UnitPrice"].ToString(),
System.Globalization.NumberStyles.Currency);
}
GridView include un QuantityPerUnit
oggetto BoundField, ma questo BoundField deve essere solo a scopo di visualizzazione e non deve essere modificabile dall'utente. Per organizzare questa operazione, è sufficiente impostare la proprietà BoundFields ReadOnly
su true
.
Figura 3: Make the BoundField Read-Only (Fare clic per visualizzare l'immagineQuantityPerUnit
full-size)
Infine, selezionare la casella di controllo Abilita modifica dal smart tag di GridView. Dopo aver completato questi passaggi, la ErrorHandling.aspx
Designer della pagina dovrebbe essere simile alla figura 4.
Figura 4: Rimuovere tutti i campi associati necessari e selezionare la casella di controllo Abilita modifica (fare clic per visualizzare l'immagine full-size)
A questo punto è disponibile un elenco di tutti i prodotti' ProductName
, e campi; tuttavia, solo i ProductName
campi , UnitPrice
UnitPrice
QuantityPerUnit
e UnitsInStock
UnitsInStock
possono essere modificati.
Figura 5: Gli utenti possono ora modificare facilmente i nomi, i prezzi e le unità nei campi azionari (fare clic per visualizzare l'immagine full-size)
Passaggio 2: Gestione dettagliata delle eccezioni DAL-Level
Anche se GridView modificabile funziona in modo meraviglioso quando gli utenti immettono valori legali per il nome, il prezzo e le unità del prodotto modificato in magazzino, immettendo valori illegali comportano un'eccezione. Ad esempio, l'omettezione del ProductName
valore causa la generazione di un'eccezione NoNullAllowedException poiché la ProductName
proprietà nella ProductsRow
classe ha la proprietà AllowDBNull
impostata su false
; se il database è inattivo, SqlException
verrà generato da TableAdapter quando si tenta di connettersi al database. Senza eseguire alcuna azione, queste eccezioni si bollano dal livello di accesso ai dati al livello di logica di business, quindi alla pagina ASP.NET e infine al runtime di ASP.NET.
A seconda del modo in cui l'applicazione Web è configurata e se si visita o meno l'applicazione da localhost
, un'eccezione non gestita può comportare una pagina generica di errore del server, un report di errore dettagliato o una pagina Web descrittiva. Vedere Gestione degli errori dell'applicazione Web in ASP.NET e l'elementocustomErrors per altre informazioni sul modo in cui il runtime di ASP.NET risponde a un'eccezione non rilevata.
La figura 6 mostra la schermata rilevata quando si tenta di aggiornare un prodotto senza specificare il ProductName
valore. Questo è il report di errore dettagliato predefinito visualizzato durante l'arrivo tramite localhost
.
Figura 6: Omettendo il nome del prodotto verranno visualizzati i dettagli delle eccezioni (fare clic per visualizzare l'immagine full-size)
Sebbene tali dettagli di eccezione siano utili durante il test di un'applicazione, la presentazione di un utente finale con una schermata di questo tipo a fronte di un'eccezione è minore dell'ideale. Un utente finale probabilmente non conosce cosa NoNullAllowedException
è o perché è stato causato. Un approccio migliore consiste nel presentare all'utente un messaggio più descrittivo che spiega che si sono verificati problemi durante il tentativo di aggiornare il prodotto.
Se si verifica un'eccezione durante l'esecuzione dell'operazione, gli eventi post-livello in ObjectDataSource e il controllo Web dati forniscono un mezzo per rilevarlo e annullare l'eccezione da bubbling fino al runtime di ASP.NET. Ad esempio, verrà creato un gestore eventi per l'evento GridView che determina se un'eccezione è stata attivata e, in tal caso, visualizza i dettagli dell'eccezione RowUpdated
in un controllo Web Etichetta.
Iniziare aggiungendo un'etichetta alla pagina ASP.NET, impostandone la ID
proprietà su ExceptionDetails
e cancellandone la Text
proprietà. Per disegnare l'occhio dell'utente a questo messaggio, impostare la relativa CssClass
proprietà su Warning
, che è una classe CSS aggiunta al Styles.css
file nell'esercitazione precedente. Tenere presente che questa classe CSS causa la visualizzazione del testo dell'etichetta in un carattere rosso, corsivo, grassetto, extra large.
Figura 7: Aggiungere un controllo Web etichetta alla pagina (fare clic per visualizzare l'immagine full-size)
Poiché si vuole che questo controllo Web etichetta sia visibile solo immediatamente dopo che si è verificata un'eccezione, impostare la relativa Visible
proprietà su false nel Page_Load
gestore eventi:
protected void Page_Load(object sender, EventArgs e)
{
ExceptionDetails.Visible = false;
}
Con questo codice, nella prima pagina visitare e postback successivi il ExceptionDetails
controllo avrà la relativa Visible
proprietà impostata su false
. A fronte di un'eccezione a livello dal-o BLL, che è possibile rilevare nel gestore eventi di GridView, verrà impostata la ExceptionDetails
proprietà del RowUpdated
Visible
controllo su true. Poiché i gestori eventi di controllo Web si verificano dopo il Page_Load
gestore eventi nel ciclo di vita della pagina, verrà visualizzata l'etichetta. Tuttavia, nel postback successivo, il Page_Load
gestore eventi restituirà nuovamente la Visible
proprietà a false
, nascondendolo di nuovo dalla visualizzazione.
Nota
In alternativa, è possibile rimuovere la necessità di impostare la proprietà del controllo in assegnandone Visible
la ExceptionDetails
proprietà false
nella sintassi dichiarativa e disabilitando lo stato di Visible
visualizzazione (impostandone la EnableViewState
proprietà su false
).Page_Load
Questo approccio alternativo verrà usato in un'esercitazione futura.
Con il controllo Label aggiunto, il passaggio successivo consiste nel creare il gestore eventi per l'evento RowUpdated
GridView. Selezionare GridView nella Designer, passare alla Finestra Proprietà e fare clic sull'icona del fulmine, elencando gli eventi di GridView. Per l'evento RowUpdating
GridView è già presente una voce, come è stato creato un gestore eventi per questo evento in precedenza in questa esercitazione. Creare anche un gestore eventi per l'evento RowUpdated
.
Figura 8: Creare un gestore eventi per l'evento RowUpdated
GridView
Nota
È anche possibile creare il gestore eventi tramite gli elenchi a discesa nella parte superiore del file di classe code-behind. Selezionare GridView nell'elenco a discesa a sinistra e l'evento RowUpdated
da quello a destra.
La creazione di questo gestore eventi aggiungerà il codice seguente alla classe code-behind della pagina di ASP.NET:
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}
Il secondo parametro di input del gestore eventi è un oggetto di tipo GridViewUpdatedEventArgs, con tre proprietà di interesse per la gestione delle eccezioni:
Exception
riferimento all'eccezione generata; se non è stata generata alcuna eccezione, questa proprietà avrà un valore dinull
ExceptionHandled
valore booleano che indica se l'eccezione è stata gestita nelRowUpdated
gestore eventi; sefalse
(impostazione predefinita), l'eccezione viene generata nuovamente, percolando fino al runtime di ASP.NETKeepInEditMode
se impostato sullatrue
riga GridView modificata rimane in modalità di modifica; sefalse
(impostazione predefinita), la riga GridView torna alla modalità di sola lettura.
Il codice, quindi, deve verificare se Exception
non null
è , vale a dire che è stata generata un'eccezione durante l'esecuzione dell'operazione. In questo caso, si vuole:
- Visualizzare un messaggio descrittivo nell'etichetta
ExceptionDetails
- Indicare che l'eccezione è stata gestita
- Mantenere la riga GridView in modalità di modifica
Questo codice seguente consente di raggiungere questi obiettivi:
protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
if (e.Exception != null)
{
// Display a user-friendly message
ExceptionDetails.Visible = true;
ExceptionDetails.Text = "There was a problem updating the product. ";
if (e.Exception.InnerException != null)
{
Exception inner = e.Exception.InnerException;
if (inner is System.Data.Common.DbException)
ExceptionDetails.Text +=
"Our database is currently experiencing problems." +
"Please try again later.";
else if (inner is NoNullAllowedException)
ExceptionDetails.Text +=
"There are one or more required fields that are missing.";
else if (inner is ArgumentException)
{
string paramName = ((ArgumentException)inner).ParamName;
ExceptionDetails.Text +=
string.Concat("The ", paramName, " value is illegal.");
}
else if (inner is ApplicationException)
ExceptionDetails.Text += inner.Message;
}
// Indicate that the exception has been handled
e.ExceptionHandled = true;
// Keep the row in edit mode
e.KeepInEditMode = true;
}
}
Questo gestore eventi inizia controllando se e.Exception
è null
. In caso contrario, la ExceptionDetails
proprietà dell'etichetta Visible
è impostata su true
e la relativa Text
proprietà su "Si è verificato un problema durante l'aggiornamento del prodotto". I dettagli dell'eccezione effettiva generata risiedono nella e.Exception
proprietà dell'oggetto InnerException
. Questa eccezione interna viene esaminata e, se si tratta di un particolare tipo, viene aggiunto un messaggio aggiuntivo utile alla ExceptionDetails
proprietà dell'etichetta Text
. Infine, le ExceptionHandled
proprietà e KeepInEditMode
sono entrambe impostate su true
.
La figura 9 mostra una schermata di questa pagina quando si omette il nome del prodotto; La figura 10 mostra i risultati quando si immette un valore non valido UnitPrice
(-50).
Figura 9: BoundField ProductName
deve contenere un valore (fare clic per visualizzare l'immagine a dimensione intera)
Figura 10: I valori negativi UnitPrice
non sono consentiti (fare clic per visualizzare l'immagine a dimensione intera)
Impostando la e.ExceptionHandled
proprietà su true
, il RowUpdated
gestore eventi ha indicato che ha gestito l'eccezione. Di conseguenza, l'eccezione non verrà propagata fino al runtime di ASP.NET.
Nota
Le figure 9 e 10 mostrano un modo normale per gestire le eccezioni generate a causa di un input utente non valido. Idealmente, tuttavia, tale input non valido non raggiungerà mai il livello della logica di business, perché la pagina ASP.NET dovrebbe garantire che gli input dell'utente siano validi prima di richiamare il ProductsBLL
metodo della UpdateProduct
classe. Nell'esercitazione successiva verrà illustrato come aggiungere controlli di convalida alle interfacce di modifica e inserimento per assicurarsi che i dati inviati al livello della logica di business siano conformi alle regole business. I controlli di convalida impediscono non solo la chiamata del UpdateProduct
metodo fino a quando i dati forniti dall'utente non sono validi, ma forniscono anche un'esperienza utente più informativa per identificare i problemi di immissione dei dati.
Passaggio 3: Gestire correttamente le eccezioni BLL-Level
Durante l'inserimento, l'aggiornamento o l'eliminazione di dati, il livello di accesso ai dati può generare un'eccezione in caso di errore correlato ai dati. Il database potrebbe essere offline, una colonna della tabella di database necessaria potrebbe non avere un valore specificato oppure è possibile che sia stato violato un vincolo a livello di tabella. Oltre alle eccezioni strettamente correlate ai dati, il livello della logica di business può usare le eccezioni per indicare quando le regole business sono state violate. Nell'esercitazione Creazione di un livello di logica di business, ad esempio, è stato aggiunto un controllo della regola business all'overload originaleUpdateProduct
. In particolare, se l'utente contrassegnava un prodotto come sospeso, era necessario che il prodotto non sia l'unico fornito dal fornitore. Se questa condizione è stata violata, è stata generata un'eccezione ApplicationException
.
Per l'overload UpdateProduct
creato in questa esercitazione, aggiungere una regola business che impedisce l'impostazione del UnitPrice
campo a un nuovo valore superiore al doppio del valore originale UnitPrice
. A tale scopo, modificare l'overload UpdateProduct
in modo che esegua questo controllo e generi un'eccezione ApplicationException
se la regola viene violata. Il metodo aggiornato segue:
public bool UpdateProduct(string productName, decimal? unitPrice, short? unitsInStock,
int productID)
{
Northwind.ProductsDataTable products = Adapter.GetProductByProductID(productID);
if (products.Count == 0)
// no matching record found, return false
return false;
Northwind.ProductsRow product = products[0];
// Make sure the price has not more than doubled
if (unitPrice != null && !product.IsUnitPriceNull())
if (unitPrice > product.UnitPrice * 2)
throw new ApplicationException(
"When updating a product price," +
" the new price cannot exceed twice the original price.");
product.ProductName = productName;
if (unitPrice == null) product.SetUnitPriceNull();
else product.UnitPrice = unitPrice.Value;
if (unitsInStock == null) product.SetUnitsInStockNull();
else product.UnitsInStock = unitsInStock.Value;
// Update the product record
int rowsAffected = Adapter.Update(product);
// Return true if precisely one row was updated, otherwise false
return rowsAffected == 1;
}
Con questa modifica, qualsiasi aggiornamento del prezzo superiore al doppio del prezzo esistente causerà la generazione di un oggetto ApplicationException
. Proprio come l'eccezione generata da DAL, questo BLL generato ApplicationException
può essere rilevato e gestito nel gestore eventi di RowUpdated
GridView. Infatti, il RowUpdated
codice del gestore eventi, come scritto, rileverà correttamente questa eccezione e visualizzerà il ApplicationException
valore della proprietà .Message
La figura 11 mostra uno screenshot quando un utente tenta di aggiornare il prezzo di Chai a $ 50,00, che è più del doppio del prezzo corrente di $ 19,95.
Figura 11: Le regole business non consentono aumenti di prezzo che più di doppio prezzo di un prodotto (fare clic per visualizzare l'immagine a dimensione intera)
Nota
Idealmente, le regole della logica di business verrebbero sottoposte a refactoring dagli overload del UpdateProduct
metodo e in un metodo comune. Questa operazione viene lasciata come esercizio per il lettore.
Riepilogo
Durante l'inserimento, l'aggiornamento e l'eliminazione di operazioni, sia il controllo Web dei dati che objectDataSource hanno coinvolto eventi pre e post-livello che consentono di modificare l'operazione effettiva. Come illustrato in questa esercitazione e in quella precedente, quando si usa un oggetto GridView modificabile viene generato l'evento gridView, seguito dall'evento RowUpdating
Updating
ObjectDataSource, a cui il comando di aggiornamento viene eseguito all'oggetto sottostante di ObjectDataSource. Al termine dell'operazione, viene generato l'evento ObjectDataSource Updated
, seguito dall'evento gridView RowUpdated
.
È possibile creare gestori eventi per gli eventi di prelivello per personalizzare i parametri di input o per gli eventi post-livello per controllare e rispondere ai risultati dell'operazione. I gestori eventi post-livello vengono usati più comunemente per rilevare se si è verificata un'eccezione durante l'operazione. In caso di eccezione, questi gestori eventi post-livello possono facoltativamente gestire l'eccezione autonomamente. In questa esercitazione è stato illustrato come gestire tale eccezione visualizzando un messaggio di errore descrittivo.
Nell'esercitazione successiva verrà illustrato come ridurre la probabilità di eccezioni derivanti da problemi di formattazione dei dati, ad esempio l'immissione di un valore negativo UnitPrice
. In particolare, si esaminerà come aggiungere controlli di convalida alle interfacce di modifica e inserimento.
Buon programmatori!
Informazioni sull'autore
Scott Mitchell, autore di sette libri ASP/ASP.NET e fondatore di 4GuysFromRolla.com, lavora con le tecnologie Web Microsoft dal 1998. Scott lavora come consulente indipendente, formatore e scrittore. Il suo ultimo libro è Sams Teach Yourself ASP.NET 2.0 in 24 ore. Può essere raggiunto all'indirizzo mitchell@4GuysFromRolla.com. o tramite il suo blog, disponibile all'indirizzo http://ScottOnWriting.NET.
Grazie speciale a
Questa serie di esercitazioni è stata esaminata da molti revisori utili. Il revisore principale per questa esercitazione è stato Liz Shulok. Si è interessati a esaminare i prossimi articoli MSDN? In tal caso, rilasciami una riga in mitchell@4GuysFromRolla.com.