Condividi tramite


Gestione delle eccezioni a livello BLL e DAL in una pagina ASP.NET (C#)

di Scott Mitchell

Scaricare il PDF

In questa esercitazione verrà illustrato come visualizzare un messaggio di errore descrittivo e informativo in caso di eccezione durante un'operazione di inserimento, aggiornamento o eliminazione di un controllo Web dati ASP.NET.

Introduzione

L'uso dei dati da un'applicazione Web ASP.NET tramite un'architettura di applicazioni a livelli prevede i tre passaggi generali seguenti:

  1. Determinare quale metodo del livello di logica di business deve essere richiamato e quali valori dei parametri da passare. I valori dei parametri possono essere integrati, assegnati tramite programmazione o valori immessi dall'utente.
  2. Richiama il metodo.
  3. Elaborare i risultati. Quando si chiama un metodo BLL che restituisce 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 la gestione normale di qualsiasi eccezione generata nel passaggio 2.

Come illustrato nell'esercitazione precedente, sia 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 alla raccolta ObjectDataSource. L'evento UpdateParameters viene generato dopo che ObjectDataSource ha completato l'operazione.

Sono già stati esaminati gli eventi generati durante il passaggio 1 e si è visto come possono essere usati per personalizzare i parametri di input o annullare l'operazione. In questo tutorial rivolgeremo la nostra attenzione agli eventi che si verificano alla conclusione dell'operazione. Con questi gestori eventi di post-livello è possibile, tra le altre cose, determinare se si è verificata un'eccezione durante l'operazione e gestirlo normalmente, visualizzando un messaggio di errore descrittivo e informativo sullo schermo invece di impostare come predefinito la pagina standard ASP.NET eccezione.

Per illustrare l'uso di questi eventi a livello di post, creare una pagina che visualizza l'elenco dei prodotti in un GridView 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. È ora di iniziare.

Passaggio 1: Creare una vista di griglia modificabile per i prodotti

Nell'esercitazione precedente, abbiamo 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 (nome del prodotto, prezzo unitario e ID) anziché un parametro per ogni campo prodotto. Per questa esercitazione, si proverà di nuovo a eseguire questa tecnica, creando un controllo GridView modificabile che visualizza il nome del prodotto, la quantità per unità, il prezzo unitario e le unità in magazzino, ma consente solo la modifica del nome, del prezzo unitario e delle unità in magazzino.

Per soddisfare questo scenario, è necessario un ulteriore overload del metodo UpdateProduct, 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;
}

Al termine di questo metodo, è possibile creare la pagina ASP.NET che consente di modificare questi quattro campi di prodotto specifici. Aprire la pagina ErrorHandling.aspx nella cartella EditInsertDelete e aggiungere un controllo GridView alla pagina tramite il Designer. Associare GridView a un nuovo ObjectDataSource, associando il metodo Select() al metodo ProductsBLL della classe GetProducts() e il metodo Update() all'overload UpdateProduct appena creato.

Usare l'overload del metodo UpdateProduct che accetta quattro parametri di input

Figura 1: Usare l'overload del UpdateProduct metodo che accetta quattro parametri di input (fare clic per visualizzare l'immagine a dimensione intera)

Verrà creato un ObjectDataSource con una collezione UpdateParameters con quattro parametri e un GridView con un campo per ognuno dei campi di prodotto. Il markup dichiarativo di ObjectDataSource assegna il valore OldValuesParameterFormatString alla proprietà original_{0}, il che causerà un'eccezione perché la classe BLL non si aspetta che venga passato un parametro di input denominato original_productID. Non dimenticare di rimuovere completamente questa impostazione dalla sintassi dichiarativa (o impostarla sul valore predefinito, {0}).

Passare quindi a ridurre il GridView per includere solo i campi ProductName, QuantityPerUnit, UnitPrice e UnitsInStock BoundFields. Inoltre, è possibile applicare qualsiasi formattazione a livello di campo che si ritiene necessario (ad esempio modificando le HeaderText proprietà).

Nell'esercitazione precedente è stato illustrato come formattare BoundField UnitPrice come valuta sia in modalità di sola lettura che in modalità di modifica. Facciamo lo stesso qui. Ricorda che ciò richiedeva di impostare la proprietà DataFormatString del BoundField su {0:c}, la sua proprietà HtmlEncode su false, e la proprietà ApplyFormatInEditMode su true, come mostrato nella figura 2.

Configurare UnitPrice BoundField per la visualizzazione come valuta

Figura 2: Configurare BoundField UnitPrice per la visualizzazione come valuta (fare clic per visualizzare l'immagine a dimensione intera)

La formattazione di UnitPrice come valuta nell'interfaccia di modifica richiede la creazione di un gestore per l'evento GridView RowUpdating che converte una stringa formattata come valuta in un valore decimal. Tenere presente che il RowUpdating gestore eventi dell'ultima esercitazione ha controllato anche per assicurarsi che l'utente abbia fornito un UnitPrice valore. Per questa esercitazione, lasciamo tuttavia che l'utente ometta 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 includono un QuantityPerUnit BoundField, ma questo BoundField deve essere utilizzato esclusivamente per la visualizzazione e non deve essere editabile dall'utente. Per disporre questa operazione, impostare semplicemente la proprietà BoundFields ReadOnly su true.

Rendere QuantityPerUnit BoundField di sola lettura

Figura 3: Impostare QuantityPerUnit l'Read-Only BoundField (fare clic per visualizzare l'immagine a dimensione intera)

Selezionare infine la casella di controllo Abilita modifica tramite lo smart tag di GridView. Dopo aver completato questi passaggi, la ErrorHandling.aspx finestra di progettazione della pagina dovrebbe essere simile alla figura 4.

Rimuovi tutti i BoundFields tranne quelli necessari e seleziona la casella di controllo Abilita modifica

Figura 4: Rimuovere tutti i BoundFields tranne quelli necessari e selezionare la casella di controllo Attiva modifica (fare clic per visualizzare l'immagine a dimensione intera)

A questo punto è disponibile un elenco di tutti i campi dei prodotti ProductName, QuantityPerUnit, UnitPrice e UnitsInStock; tuttavia, è possibile modificare solo i campi ProductName, UnitPrice e UnitsInStock.

Gli utenti possono ora modificare facilmente i nomi, i prezzi e le unità dei prodotti nei campi azionari

Figura 5: Gli utenti possono ora modificare facilmente i nomi, i prezzi e le unità dei prodotti nei campi azionari (fare clic per visualizzare l'immagine a dimensione intera)

Passaggio 2: Gestire correttamente le eccezioni DAL-Level

Anche se il GridView modificabile funziona meravigliosamente quando gli utenti inseriscono valori validi per il nome, il prezzo e le unità in magazzino del prodotto modificato, inserire valori non validi comporta un'eccezione. Se ad esempio il valore viene ProductName omesso, viene generata un'eccezione NoNullAllowedException poiché la ProductName proprietà nella ProductsRow classe ha la proprietà AllowDBNull impostata su false. Se il database è inattivo, viene generata un'eccezione SqlException da TableAdapter quando si tenta di connettersi al database. Senza eseguire alcuna azione, queste eccezioni passano dal livello di accesso ai dati al livello della 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 intuitiva. Per altre informazioni su come il runtime di ASP.NET risponde a un'eccezione non rilevata, vedere Gestione degli errori dell'applicazione Web in ASP.NET e l'elemento customErrors .

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 quando si passa attraverso localhost.

Omettendo il nome del prodotto verranno visualizzati i dettagli dell'eccezione

Figura 6: Omettendo il nome del prodotto verranno visualizzati i dettagli dell'eccezione (fare clic per visualizzare l'immagine a dimensione intera)

Anche se tali dettagli di eccezione sono utili durante il test di un'applicazione, la presentazione di un utente finale con tale schermata in presenza di un'eccezione è minore dell'ideale. Un utente finale probabilmente non sa che cos'è 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 di post-livello sia dell'ObjectDataSource sia del controllo dati Web forniscono un mezzo per rilevarla e impedire che l'eccezione si propaghi fino al runtime di ASP.NET. Per questo esempio, creiamo un gestore eventi per l'evento gridView RowUpdated che determina se un'eccezione è stata attivata e, in tal caso, visualizza i dettagli dell'eccezione in un controllo Web Label.

Per iniziare, aggiungere un'etichetta alla pagina ASP.NET, impostando la proprietà ID su ExceptionDetails e cancellando la proprietà Text. Per attirare l'attenzione dell'utente su questo messaggio, imposta la sua proprietà CssClass su Warning, ovvero una classe CSS che abbiamo aggiunto al file Styles.css nell'esercitazione precedente. Tenere presente che questa classe CSS fa sì che il testo dell'etichetta venga visualizzato in un tipo di carattere rosso, corsivo, grassetto, extra grande.

Aggiungere un controllo Web di etichetta alla pagina

Figura 7: Aggiungere un controllo Web etichetta alla pagina (fare clic per visualizzare l'immagine a dimensione intera)

Poiché vogliamo che questo controllo Web di etichetta sia visibile solo immediatamente dopo il verificarsi di un'eccezione, impostare la sua proprietà Visible su false nel gestore eventi Page_Load.

protected void Page_Load(object sender, EventArgs e)
{
    ExceptionDetails.Visible = false;
}

Con questo codice, alla prima visita della pagina e ai successivi postback, il controllo ExceptionDetails avrà la proprietà Visible impostata su false. In presenza di un'eccezione a livello DAL o BLL, che è possibile rilevare nel gestore eventi di RowUpdated GridView, la ExceptionDetails proprietà del Visible controllo verrà impostata su true. Poiché i gestori di eventi del controllo Web si verificano dopo il Page_Load gestore di eventi nel ciclo di vita della pagina, verrà visualizzata l'Etichetta. Tuttavia, nel postback successivo, il gestore eventi Page_Load ripristinerà la proprietà Visible a false, nascondendola nuovamente alla vista.

Annotazioni

In alternativa, è possibile rimuovere la necessità di impostare la ExceptionDetails proprietà del Visible controllo in Page_Load assegnandone Visible la proprietà false nella sintassi dichiarativa e disabilitandone lo stato di visualizzazione (impostandone la EnableViewState proprietà su false). 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 gridView RowUpdated . Selezionare GridView in Progettazione, passare alla finestra Proprietà e fare clic sull'icona a forma di fulmine, elencando gli eventi di GridView. Dovrebbe già esserci una voce per l'evento del GridView RowUpdating, poiché abbiamo precedentemente creato un gestore per questo evento in questo tutorial. Creare anche un gestore eventi per l'evento RowUpdated .

Creare un gestore eventi per l'evento RowUpdated di GridView

Figura 8: Creare un gestore eventi per l'evento di RowUpdated GridView

Annotazioni

È anche possibile creare il gestore eventi tramite gli elenchi a discesa nella parte superiore del file di classe code-behind. Seleziona "GridView" dall'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 ASP.NET:

protected void GridView1_RowUpdated(object sender, GridViewUpdatedEventArgs e)
{
}

Il secondo parametro di input del gestore eventi è un oggetto di tipo GridViewUpdatedEventArgs, che ha 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 pari a null
  • ExceptionHandled Valore booleano che indica se l'eccezione è stata gestita nel RowUpdated gestore eventi; se false (impostazione predefinita), l'eccezione viene generata nuovamente, percolando fino al runtime ASP.NET
  • KeepInEditMode se impostato sulla true riga gridView modificata rimane in modalità di modifica; se false (impostazione predefinita), la riga GridView torna alla modalità di sola lettura

Il nostro codice, quindi, dovrebbe verificare se Exception non è null, il che significa che è stata sollevata 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. Se non è così, la proprietà ExceptionDetails dell'etichetta Visible viene impostata su true e la sua proprietà Text su "Si è verificato un problema durante l'aggiornamento del prodotto". I dettagli dell'eccezione effettiva generata risiedono nella proprietà e.Exception dell'oggetto InnerException. Questa eccezione interna viene esaminata e, se è di un tipo particolare, viene aggiunto un messaggio utile e aggiuntivo ExceptionDetails alla proprietà Text dell'etichetta. 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).

ProductName BoundField deve contenere un valore

Figura 9: BoundField ProductName deve contenere un valore (fare clic per visualizzare l'immagine a dimensione intera)

Prezzi unitari negativi non sono consentiti

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. Pertanto, l'eccezione non verrà propagata fino al runtime di ASP.NET.

Annotazioni

Le figure 9 e 10 mostrano un modo normale per gestire le eccezioni generate a causa dell'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 assicurarsi 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 non solo impediscono 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 aziendale, ad esempio, è stata aggiunta una verifica della regola aziendale al sovraccarico originale.UpdateProduct In particolare, se l'utente contrassegnava un prodotto come sospeso, abbiamo richiesto che il prodotto non sia l'unico fornito dal suo fornitore. Se questa condizione è stata violata, è stata generata un'eccezione ApplicationException .

Per l'overload UpdateProduct creato in questa esercitazione, aggiungere una regola aziendale che vieta l'assegnazione al campo UnitPrice di un nuovo valore che sia maggiore del doppio del valore originale UnitPrice. A tale scopo, regolare 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 che sia più del doppio del prezzo esistente verrà generata un'eccezione ApplicationException. Proprio come l'eccezione generata dal DAL, questa BLL-generata ApplicationException può essere rilevata e gestita nel gestore eventi di GridView RowUpdated. Infatti, il codice del gestore eventi RowUpdated, come scritto, rileverà correttamente questa eccezione e visualizzerà il valore della proprietà ApplicationException di 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.

Le regole aziendali vietano aumenti di prezzo che superano più del doppio il prezzo di un prodotto.

Figura 11: Le regole di business non consentono aumenti di prezzo che più del doppio prezzo di un prodotto (fare clic per visualizzare l'immagine a dimensione intera)

Annotazioni

Idealmente, le regole della logica di business dovrebbero essere refattorizzate dagli overload del metodo UpdateProduct e in un metodo comune. Questa operazione viene lasciata come esercizio per il lettore.

Riassunto

Durante l'inserimento, l'aggiornamento e l'eliminazione delle operazioni, sia il controllo Web dei dati che l'ObjectDataSource attivano eventi pre-operativi e post-operativi che delimitano l'operazione effettiva. Come abbiamo visto in questo tutorial e in quello precedente, quando si lavora con un GridView modificabile, viene attivato l'evento RowUpdating del GridView, seguito dall'evento Updating dell'ObjectDataSource, momento in cui il comando di aggiornamento viene eseguito sull'oggetto sottostante di ObjectDataSource. Al termine dell'operazione, viene generato l'evento ObjectDataSourceUpdated, 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 di eventi a livello post possono scegliere di 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, verrà illustrato 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, ha lavorato 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 a mitchell@4GuysFromRolla.com.

Grazie speciale a

Questa serie di esercitazioni è stata esaminata da molti revisori competenti. Il revisore principale per questo tutorial è Liz Shulok. Si è interessati a esaminare i prossimi articoli MSDN? In tal caso, mandami un messaggio a mitchell@4GuysFromRolla.com.