Controlar las excepciones de nivel BLL y DAL en una página de ASP.NET (VB)

por Scott Mitchell

Descargar PDF

En este tutorial verá cómo mostrar un mensaje de error descriptivo e informativo si se produce una excepción durante una operación de inserción, actualización o eliminación de un control web de datos ASP.NET.

Introducción

Trabajar con datos de una aplicación web ASP.NET mediante una arquitectura de aplicación en capas implica los tres pasos generales siguientes:

  1. Determinar qué método de la capa de lógica de negocios se debe invocar y qué valores de parámetro se le van a pasar. Los valores de parámetro se pueden codificar de forma rígida, asignarse mediante programación o con entradas introducidas por el usuario.
  2. Invoque al método.
  3. Procesar los resultados. Al llamar a un método BLL que devuelve datos, esto puede implicar el enlace de los datos a un control web de datos. En el caso de los métodos BLL que modifican datos, esto puede incluir realizar alguna acción en función de un valor devuelto, o bien controlar correctamente cualquier excepción que haya surgido en el paso 2.

Como ha visto en el tutorial anterior, ObjectDataSource y los controles web de datos proporcionan puntos de extensibilidad para los pasos 1 y 3. GridView, por ejemplo, desencadena su evento RowUpdating antes de asignar sus valores de campo a su colección UpdateParameters de ObjectDataSource; su evento RowUpdated se genera después de que ObjectDataSource haya completado la operación.

Ya se han examinado los eventos que se desencadenan durante el paso 1 y ha visto cómo se pueden usar para personalizar los parámetros de entrada o cancelar la operación. En este tutorial, se centrará en los eventos que se activan después de que se haya completado la operación. Con estos controladores de eventos posteriores, entre otras cosas puede determinar si se ha producido una excepción durante la operación y controlarla correctamente, y mostrar un mensaje de error descriptivo e informativo en la pantalla en lugar de la página predeterminado de excepción de ASP.NET estándar.

Para ilustrar cómo trabajar con estos eventos de nivel posteriores, creará una página en la que se muestran los productos en un control GridView modificable. Al actualizar un producto, si se produce una excepción en la página ASP.NET, se mostrará un mensaje corto encima de GridView en el que explica que se ha producido un problema. Comencemos.

Paso 1: Creación de un control GridView modificable de productos

En el tutorial anterior ha creado control GridView modificable con solo dos campos, ProductName y UnitPrice. Para ello, ha sido necesario crear una sobrecarga adicional para el método UpdateProduct de la clase ProductsBLL, que solo aceptaba tres parámetros de entrada (el nombre del producto, el precio unitario y el identificador) en lugar de un parámetro para cada campo de producto. Para este tutorial, volverá a practicar con esta técnica y creará un control GridView modificable en el que se muestre el nombre del producto, la cantidad por unidad, el precio unitario y las unidades en existencias, pero en el que solo se permite editar el nombre, el precio unitario y las unidades en existencias.

Para dar cabida a este escenario, necesitará otra sobrecarga del método UpdateProduct que acepte cuatro parámetros: el nombre del producto, el precio unitario, las unidades en existencias y el identificador. Agregue el siguiente método a la clase 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

Con este método completo, ya puede crear la página ASP.NET que permite editar estos cuatro campos de producto concretos. Abra la página ErrorHandling.aspx en la carpeta EditInsertDelete y agregue un control GridView a la página desde el Diseñador. Enlace GridView a un objeto ObjectDataSource nuevo, y asigne el método Select() al método GetProducts() de la clase ProductsBLL y el método Update() a la sobrecarga de UpdateProduct recién creada.

Use the UpdateProduct Method Overload That Accepts Four Input Parameters

Figura 1: Uso de la sobrecarga del método UpdateProduct que acepta cuatro parámetros de entrada (Haga clic para ver la imagen a tamaño completo)

Esto creará una instancia de ObjectDataSource con una colección UpdateParameters con cuatro parámetros y un control GridView con un campo para cada uno de los campos de producto. El marcado declarativo de ObjectDataSource asigna a la propiedad OldValuesParameterFormatString el valor original_{0}, lo que provocará una excepción, ya que la clase BLL no espera que se pase un parámetro de entrada denominado original_productID. No olvide quitar este valor de la sintaxis declarativa (o establézcalo en el valor predeterminado, {0}).

A continuación,reduzca GridView para incluir solo los controles BoundField ProductName, QuantityPerUnit, UnitPrice y UnitsInStock. También puede aplicar cualquier formato de nivel de campo que considere necesario (por ejemplo, cambiar las propiedades HeaderText).

En el tutorial anterior ha visto cómo dar formato a la instancia de BoundField UnitPrice como moneda, tanto en modo de solo lectura como en modo de edición. Hará lo mismo aquí. Recuerde que para esto era necesario establecer la propiedad DataFormatString de BoundField en {0:c}, su propiedad HtmlEncode en false y ApplyFormatInEditMode en true, como se muestra en la figura 2.

Configure the UnitPrice BoundField to Display as a Currency

Figura 2: Configuración de la instancia de BoundField UnitPrice para mostrarla como moneda (Haga clic para ver la imagen a tamaño completo)

Para aplicar formato de moneda a UnitPrice en la interfaz de edición es necesario crear un controlador de eventos para el evento RowUpdating de GridView que analiza la cadena con formato de moneda en un valor decimal. Recuerde que el controlador de eventos RowUpdating del último tutorial también se comprueba para asegurarse de que el usuario ha proporcionado un valor UnitPrice. Pero es este tutorial se permitirá que el usuario omita el precio.

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

El control GridView incluye una instancia de BoundField QuantityPerUnit, pero solo con fines de visualización y no debe ser modificable por el usuario. Para solucionarlo, simplemente establezca la propiedad ReadOnly de BoundField en true.

Make the QuantityPerUnit BoundField Read-Only

Figura 3: Procedimiento para que la instancia de BoundField QuantityPerUnit sea de solo lectura (Haga clic para ver la imagen a tamaño completo)

Por último, active la casilla Habilitar edición en la etiqueta inteligente de GridView. Después de completar estos pasos, el Diseñador de la página ErrorHandling.aspx debe ser similar al de la figura 4.

Remove All But the Needed BoundFields and Check the Enable Editing Checkbox

Figura 4: Eliminación de todos los campos BoundField menos los necesarios y activación de la casilla Habilitar edición (Haga clic para ver la imagen a de tamaño completo)

En este punto hay una lista de campos ProductName, QuantityPerUnit, UnitPrice y UnitsInStock de todos los productos; pero solo se pueden editar los campos ProductName, UnitPrice y UnitsInStock.

Users Can Now Easily Edit Products' Names, Prices, and Units In Stock Fields

Figura 5: Ahora los usuarios pueden editar fácilmente los campos de nombre, precio y unidades en existencias de los productos (Haga clic para ver la imagen a tamaño completo)

Paso 2: Control correcto de excepciones de nivel DAL

Aunque el control GridView modificable funciona a la perfección cuando los usuarios escriben valores válidos para el nombre, el precio y las unidades en existencia del producto editados, la especificación de valores no válidos genera una excepción. Por ejemplo, si se omite el valor ProductName, se inicia una excepción NoNullAllowedException, ya que la propiedad ProductName de la clase ProductsRow tiene su propiedad AllowDBNull establecida en false; si la base de datos está inactiva, TableAdapter iniciará una excepción SqlException al intentar la conexión a la base de datos. Si no se realiza ninguna acción, estas excepciones se propagan desde la capa de acceso a datos hasta la capa de lógica de negocios, después a la página ASP.NET y, por último, al runtime de ASP.NET.

En función de cómo se configure la aplicación web y de si la visita desde localhost o no, una excepción no controlada puede dar lugar a una página genérica de error de servidor, un informe de errores detallado o una página web descriptiva. Vea Control de errores de aplicación web en ASP.NET y el elemento customErrors para más información sobre cómo responde el runtime de ASP.NET a una excepción no detectada.

En la figura 6 se muestra la pantalla que aparece al intentar actualizar un producto sin especificar el valor ProductName. Es el informe de errores detallado predeterminado que se muestra al pasar por localhost.

Omitting the Product's Name Will Display Exception Details

Figura 6: Si se omite el nombre del producto se mostrarán los detalles de la excepción (Haga clic para ver la imagen a tamaño completo)

Aunque estos detalles de excepción son útiles al probar una aplicación, presentar a un usuario final este tipo de pantalla cuando se produce una excepción no es lo ideal. Es probable que un usuario final no sepa qué es NoNullAllowedException o a qué se debe. Un mejor enfoque consiste en presentar al usuario un mensaje más descriptivo que explique que se han producido problemas al intentar actualizar el producto.

Si se produce una excepción al realizar la operación, los eventos de nivel posterior tanto en ObjectDataSource como en el control web de datos proporcionan un medio para detectarla y evitar que se propague hasta el runtime de ASP.NET. En el ejemplo, se creará un controlador de eventos para el evento RowUpdated de GridView que determina si se ha iniciado una excepción y, en ese caso, mostrar los detalles de la excepción en un control web de etiqueta.

Para empezar, agregue una etiqueta a la página ASP.NET, establezca su propiedad ID en ExceptionDetails y borre su propiedad Text. Para captar la atención del usuario en este mensaje, establezca su propiedad CssClass en Warning, que es una clase CSS que ha agregado al archivo Styles.css en el tutorial anterior. Recuerde que esta clase CSS hace que el texto de la etiqueta se muestre en una fuente de color rojo, en cursiva, en negrita y extra grande.

Add a Label Web Control to the Page

Figura 7: Adición de un control web de etiqueta a la página (Haga clic para ver la imagen a tamaño completo)

Como quiere que este control web de etiqueta sea visible solo inmediatamente después de que se haya producido una excepción, establezca su propiedad Visible en false en el controlador de eventos Page_Load:

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

Con este código, en la primera visita a la página y en posteriores postbacks, el control ExceptionDetails tendrá su propiedad Visible establecida en false. En caso de una excepción de nivel DAL o BLL, que se puede detectar en el controlador de eventos RowUpdated de GridView, se establecerá la propiedad Visible del control ExceptionDetails en true. Como los controladores de eventos de control web se producen después del controlador de eventos Page_Load en el ciclo de vida de la página, se mostrará la etiqueta. Pero en el siguiente postback, el controlador de eventos Page_Load revertirá la propiedad Visible a falsey la ocultará de nuevo en la vista.

Nota:

Como alternativa, se podría eliminar la necesidad de establecer la propiedad Visible del control ExceptionDetails en Page_Load y asignar su propiedad Visiblefalseen la sintaxis declarativa y deshabilitar su estado de visualización (mediante el establecimiento de su propiedad EnableViewState en false). En un tutorial futuro se usará este enfoque alternativo.

Después de agregar el control de etiqueta, el siguiente paso consiste en crear el controlador de eventos para el evento RowUpdated de GridView. Seleccione el control GridView en el Diseñador, vaya a la ventana Propiedades y haga clic en el icono de rayo, para mostrar los eventos de GridView. Ya debería haber una entrada para el evento RowUpdating de GridView, porque anteriormente en este tutorial se ha creado un controlador de eventos para este evento. Cree también un controlador de eventos para el evento RowUpdated.

Create an Event Handler for the GridView's RowUpdated Event

Figura 8: Creación de un controlador de eventos para el evento RowUpdated de GridView

Nota:

También puede crear el controlador de eventos desde las listas desplegables en la parte superior del archivo de clase de código subyacente. Seleccione GridView en la lista desplegable de la izquierda y el evento RowUpdated en la derecha.

Al crear este controlador de eventos, se agregará el código siguiente a la clase de código subyacente de la página ASP.NET:

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

El segundo parámetro de entrada de este controlador de eventos es un objeto de tipo GridViewUpdatedEventArgs, que tiene tres propiedades de interés para controlar excepciones:

  • Exception una referencia a la excepción iniciada; si no se ha iniciado ninguna excepción, esta propiedad tendrá un valor de null
  • ExceptionHandled un valor booleano que indica si la excepción se ha controlado o no en el controlador de eventos RowUpdated; si es false (el valor predeterminado), se vuelve a iniciar la excepción y se propaga hasta el runtime de ASP.NET
  • Si KeepInEditMode se establece en true, la fila de GridView editada permanece en modo de edición; si es false (el valor predeterminado), la fila GridView vuelve a su modo de solo lectura

Después, el código debe comprobar si Exception no es null, lo que significa que se ha iniciado una excepción al realizar la operación. En ese caso, le interesa lo siguiente:

  • Mostrar un mensaje descriptivo en la etiqueta ExceptionDetails
  • Indicar que la excepción se ha controlado
  • Mantener la fila de GridView en modo de edición

En el código siguiente se logran estos objetivos:

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

Para empezar, este controlador de eventos comprueba si e.Exception es null. De lo contrario, la propiedad Visible del control Label ExceptionDetails se establece en true y su propiedad Text en "Se ha producido un problema al actualizar el producto". Los detalles de la excepción real que se ha iniciado residen en la propiedad InnerException del objeto e.Exception. Se examina esta excepción interna y, si es de un tipo determinado, se anexa un mensaje adicional útil a la propiedad Text del control Label ExceptionDetails. Por último, las propiedades ExceptionHandled y KeepInEditMode se establecen en true.

En la figura 9 se muestra una captura de pantalla de esta página al omitir el nombre del producto; en la figura 10 se muestran los resultados al escribir un valor UnitPrice no válido (-50).

The ProductName BoundField Must Contain a Value

Figura 9: El control BoundField ProductName debe contener un valor (Haga clic para ver la imagen a tamaño completo)

Negative UnitPrice Values are Not Allowed

Figura 10: No se permiten valores UnitPrice negativos (Haga clic para ver la imagen a tamaño completo)

Al establecer la propiedade.ExceptionHandled en true, el controlador de eventos RowUpdated ha indicado que ha controlado la excepción. Por tanto, la excepción no se propagará hasta el runtime de ASP.NET.

Nota:

En las figuras 9 y 10 se muestra una manera correcta de controlar las excepciones generadas debido a una entrada de usuario no válida. Lo ideal es que esta entrada no válida nunca llegue a la capa de lógica de negocio, ya que la página ASP.NET debe asegurarse de que las entradas del usuario sean válidas antes de invocar el método UpdateProduct de la clase ProductsBLL. En el siguiente tutorial verá cómo agregar controles de validación a las interfaces de edición e inserción para asegurarse de que los datos enviados a la capa de lógica de negocios se ajustan a las reglas de negocio. Los controles de validación no solo impiden la invocación del método UpdateProduct hasta que los datos proporcionados por el usuario sean válidos, sino que también proporcionan una experiencia del usuario más informativa para identificar problemas de entrada de datos.

Paso 3: Control correcto de excepciones de nivel BLL

Al insertar, actualizar o eliminar datos, la capa de acceso a datos puede iniciar una excepción si se produce un error relacionado con los datos. La base de datos puede estar sin conexión, es posible que una columna de tabla de base de datos obligatoria no tenga un valor especificado o que se haya infringido una restricción de nivel de tabla. Además de excepciones estrictamente relacionadas con los datos, la capa de lógica de negocios puede usar excepciones para indicar cuándo se han infringido las reglas de negocio. En el tutorial Creación de una capa de lógica de negocios, por ejemplo, se ha agregado una comprobación de reglas de negocios a la sobrecarga de UpdateProduct original. En concreto, si el usuario marca un producto como sin existencia, era necesario que el producto no fuera el único proporcionado por su proveedor. Si se infringía esta condición, se iniciaba una excepción ApplicationException.

Para la sobrecarga de UpdateProduct creada en este tutorial, se agregará una regla de negocios que prohíbe que el campo UnitPrice se establezca en un nuevo valor que sea superior al doble del valor UnitPrice original. Para ello, ajuste la sobrecarga de UpdateProduct para que realice esta comprobación e inicie una excepción ApplicationException si se infringe la regla. El método actualizado es el siguiente:

<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

Con este cambio, cualquier actualización de precios que sea más del doble del precio existente hará que se inicie una excepción ApplicationException. Como sucede con la excepción generada desde DAL, esta excepción ApplicationException generada por BLL se puede detectar y controlar en el controlador de eventos RowUpdated de GridView. De hecho, el código del controlador de eventos RowUpdated, como se ha escrito, detectará correctamente esta excepción y mostrará el valor de propiedad Message de ApplicationException. En la figura 11 se muestra una captura de pantalla del intento de un usuario de actualizar el precio de Chai a 50 USD, que es más del doble de su precio actual de 19,95 USD.

The Business Rules Disallow Price Increases That More Than Double a Product's Price

Figura 11: Las reglas de negocios no permiten aumentos de precio que superan el doble del precio de un producto (Haga clic para ver la imagen a tamaño completo)

Nota:

Idealmente, las reglas de lógica de negocios se refactorizarían a partir de las sobrecargas del método UpdateProduct en un método común. Esta operación se deja como ejercicio para el lector.

Resumen

Durante las operaciones de inserción, actualización y eliminación, tanto el control web de datos como el elemento ObjectDataSource implicado desencadenan eventos previos y posteriores que delimitan la operación real. Como ha visto en este tutorial y en el anterior, cuando se trabaja con un control GridView modificable, se desencadena el eventoRowUpdating de GridView, seguido del evento Updating de ObjectDataSource, momento en el que el comando de actualización llega al objeto subyacente de ObjectDataSource. Una vez que se completa la operación, se desencadena el evento Updated de ObjectDataSource, seguido del evento RowUpdated de GridView.

Se pueden crear controladores de eventos para los eventos de nivel previo a fin de personalizar los parámetros de entrada, o bien para los eventos de nivel posterior a fin de inspeccionar y responder a los resultados de la operación. Los controladores de eventos de nivel posterior se usan normalmente para detectar si se ha producido una excepción durante la operación. En el caso de una excepción, estos controladores de eventos de nivel posterior pueden controlarla opcionalmente por su cuenta. En este tutorial ha visto cómo controlar esta excepción mediante la representación de un mensaje de error descriptivo.

En el siguiente tutorial, verá cómo reducir la probabilidad de que se produzcan excepciones derivadas de problemas de formato de datos (por ejemplo, escribir un valor UnitPrice negativo). En concreto, se verá cómo agregar controles de validación a las interfaces de edición e inserción.

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 in 24 Hours. Puede ponerse en contacto con él en mitchell@4GuysFromRolla.com. o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Muchos revisores han evaluado esta serie de tutoriales. El revisor principal de este tutorial ha sido Liz Shulok. ¿Le interesa revisar mis próximos artículos de MSDN? Si fuera así, escríbame a mitchell@4GuysFromRolla.com.