Creación de una Aplicación Multicapas con ASPNET 2005 Miguel Ángel Niño Zambrano Adaptación de: Building Layered Web Applications with Microsoft ASP.NET 2.0 - Part 1-2-3. http://imar.spaanjaars.com/QuickDocId.aspx?quickdoc=416
Objetivos Diseñar una aplicación muticapa, separando adecuadamente la presentación, el negocio y los datos. Diseñar, construir e implementar clases orientadas a objetos reusable de fácil mantenimiento. Crear objetos de negocio personalizados. Usar Visual Studio .Net 2005 para desarrollar la aflicción Web con este estilo arquitectónico.
Arquitectura de la Aplicación Presentation layer – PL. Business Logic Layer – BLL. Data Access Layer – DAL. Objetivo primordial es poder cambiar una o mas capas si afectar las otras.
Arquitectura de la Aplicación La PL pregunta al BLL por algún objeto, por ejemplo, el contacto de una persona. La BLL puede opcionalmente desarrollar alguna validación (por ejemplo, ¿Está habilitado el usuario actual para hacer ésta llamada?) y después llama al DAL. El DAL conecta a la base de datos y pregunta por un registro específico. Cuando el registro es encontrado, éste es retornado de la base de datos al DAL. El DAL empaqueta los datos de la base de datos en un objeto personalizado y lo retorna AL BLL. Finalmente, el BLL retorna el objeto al PL, donde podrías ser mostrado en una página Web.
El modelo Espagueti protected void Page_Load(object sender, EventArgs e) { if (!Page.IsPostBack) { string sql = @"SELECT FirstName, LastName, MiddleName FROM ContactPerson WHERE Id = 1"; using (SqlConnection myConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["NLayer"].ConnectionString)) using (SqlCommand myCommand = new SqlCommand(sql, myConnection)) myConnection.Open(); using (SqlDataReader myReader = myCommand.ExecuteReader()) if (myReader.Read()) { txtFirstName.Text = myReader.GetString(0); txtLastName.Text = myReader.GetString(1); if (!myReader.IsDBNull(2)) { txtMiddleName.Text = myReader.GetString(2); } myReader.Close(); myConnection.Close(); Dificultad para Escribir y recordar No se puede reutilizar éste código, hay que copiarlo y pegarlo todo.
El modelo Espagueti protected void btnSave_Click(object sender, EventArgs e) { string sqlBase = @"UPDATE ContactPerson SET FirstName='{0}', LastName='{1}', MiddleName ='{2}' WHERE Id = 1"; using (SqlConnection myConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["NLayer"].ConnectionString)) string sql = String.Format(sqlBase, txtFirstName.Text, txtLastName.Text, txtMiddleName.Text); SqlCommand myCommand = new SqlCommand(sql, myConnection); myConnection.Open(); myCommand.ExecuteNonQuery(); myConnection.Close(); } Abre una conexión por cada operación SQL injection La capa de datos y lógica de negocio están revueltas
Controles SqlDataSource <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:NLayer %>" DeleteCommand="DELETE FROM [ContactPerson] WHERE [Id] = @original_Id" InsertCommand="INSERT INTO [ContactPerson] ([FirstName], [MiddleName], [LastName], [DateOfBirth], [ContactPersonType]) VALUES (@FirstName, @MiddleName, @LastName, @DateOfBirth, @ContactPersonType)" SelectCommand="SELECT * FROM [ContactPerson]" UpdateCommand="UPDATE [ContactPerson] SET [FirstName] = @FirstName, [MiddleName] = @MiddleName, [LastName] = @LastName, [DateOfBirth] = @DateOfBirth, [ContactPersonType] = @ContactPersonType WHERE [Id] = @original_Id" OldValuesParameterFormatString="original_{0}" > <DeleteParameters> <asp:Parameter Name="original_Id" Type="Int32" /> </DeleteParameters> <UpdateParameters> <asp:Parameter Name="FirstName" Type="String" /> <asp:Parameter Name="MiddleName" Type="String" /> <asp:Parameter Name="LastName" Type="String" /> <asp:Parameter Name="DateOfBirth" Type="DateTime" /> <asp:Parameter Name="ContactPersonType" Type="Int32" /> <asp:Parameter Name="original_Id" Type="Int32" /> </UpdateParameters> <InsertParameters> <asp:Parameter Name="FirstName" Type="String" /> <asp:Parameter Name="MiddleName" Type="String" /> <asp:Parameter Name="LastName" Type="String" /> <asp:Parameter Name="DateOfBirth" Type="DateTime" /> <asp:Parameter Name="ContactPersonType" Type="Int32" /> </InsertParameters> </asp:SqlDataSource> CRUD Expone la lógica de los datos en la página No se puede reutilizar. Un nuevo campo debe actualizar el control. Opción 2: TableAdapter
Metodología de Desarrollo - DRA Definición de la Arquitectura (Diagrama casos de uso reales, Despliegue, Aplicación, etc.) Análisis de Requerimientos (Ej. Análisis de Datos y Flujos de Datos, Diseño de Interfaces (HCI), story board, Análisis de Procesos) Diseño de la Aplicación Diseño de la Capa de Lógica de Negocio - BLL Diseño Clases Diseño del Modelo de Objetos Diseño de la Capa de Datos – DAL Diseño Separación Lógica de capas. (Namespaces) Diseño de la Base de Datos Implementación de la Aplicación Pruebas y Despliegue
Definición de la Arquitectura del Sistema Estilo Arquitectónico: Capas y Patrones, Casos Reales.
Decisiones de Arquitectura Aplicación Web cliente – Servidor. Estilo arquitectónico Multicapa: Presentación – Negocio – Persistencia. Uso de Patrones MVC y Proxy. Orientado a Objetos y Componentes. Uso de las mejores prácticas.
Introducción a la Aplicación de Ejemplo Casos de Uso Reales
Análisis de Requerimientos Lista de Reglas de Negocio, Informes, Reportes, Restricciones.
Requerimientos de la aplicación Reglas del Negocio La aplicación debe ser capaz de ver una lista de todas las personas de contacto en el sistema. Además, ella debería ser capaz de crear nuevo, y cambiar y suprimir a personas de contacto existentes. La aplicación debe ser capaz de seleccionar a una persona de contacto específica, y luego conseguir una lista de sus direcciones asociadas. Además, ella debería ser capaz de crear nuevo y cambiar y suprimir direcciones existentes para la persona de contacto. La aplicación debe ser capaz de seleccionar a una persona de contacto específica, y luego conseguir una lista de sus direcciones de correo electrónico asociadas. Además, ella debería ser capaz de crear nuevo y cambiar y suprimir direcciones de correo electrónico existentes para la persona de contacto. El usuario del uso debe ser capaz de seleccionar a una persona de contacto específica, y luego conseguir una lista de sus números de teléfono asociados. Además, ella debería ser capaz de crear nuevo y cambiar y suprimir números de teléfono existentes para la persona de contacto.
Requerimientos de la aplicación Consultas Presentar la información de los usuarios que coinciden con un nombre de usuario. Presentar la información de los contactos que están clasificados como amigos Informes Obtener un informe de todos los contactos y clasificado por tipo de contacto. Restricciones Un contacto no puede tener más de dos direcciones.
Diseño de la Aplicación Capa lógica del Negocio BLL y Capa de Datos DAL
Diseño de Clases Del discurso anterior se debe diseñar las siguientes clases: ContactPerson Address EmailAddress PhoneNumber Mejores prácticas de diseño: ¿Qué acciones deberían realizar los objetos del negocio? Como una persona puede tener varios datos de la misma clase (números telefónicos y direcciones) entonces de necesita manejar una colección. Almacenar las clases por separado vs. un solo archivo de clase. Decide por separado con una clase que se encarga de administrar su conexión con los datos. Diseño Borrador
Diseño de Clases Métodos de ContactPerson que se compartirán entre los objetos del negocio: Métodos CRUD GetItem GetList Insert Update Delete Métodos específicos del Negocio Search Desactivate Clone Filter Por facilidad se usarán sólo los CRUD.
Diseño del Modelo de Objetos – Primera Aproximación Usar una herramienta CASE. Existen dos enumeraciones ContactType y PersonType. La clase Address no tiene aún definido el tipo (type). Las clases PhoneNumber y EmailAddress no tienen CRUD. La clase ContactPerson no tiene propiedades para referenciar las clases PhoneNumber y EmailAddress. Es conveniente tener éstos tipos de datos en una colección: Ej. Address (List<Address>), PhoneNumber (List<PhoneNumber>). Para acceder al nombre completo debería crearse una propiedad adecuada FULLNAME. Los métodos Update e Insert se pueden unificar en un implementación UPsert.
Diseño del Modelo de Objetos – Segunda Aproximación La clase ContactPerson tiene propiedades PhoneNumbers y EmailAddresses, con los tipos de su respectiva colección. Para trabajar con una lista de personas se desarrollo un ContactPersonList. Se añadió un valor NotSet a las enumeraciones, diciendo que los valores no están determinados aún.
Diseño del Modelo de Objetos – Tercera Aproximación Además de las clases anteriores se debe crea las clases manejadoras, según la decisión de diseño tomada. Cada una de éstas clases tienen una referencia a los objetos del negocio anterior. La implementación es: GetItem devuelve un caso de la clase, GetList devuelve una lista de los casos de la clase, mientras Save acepta un caso de la clase que debe ser almacenada en la base de datos. Finalmente, el método Delete acepta un caso de la clase también, conteniendo el artículo que debería ser suprimido. GetItem para el ContactPersonManager tiene una sobrecarga que le permite para determinar si usted quiere cargar sólo los datos básicos para un ContactPerson, o todos los datos asociados. Los GetList reciben sólo el id del ContactPerson y con éste ya conoce que desea traer Address, PhoneNumber, etc.
Diseño de la Capa de Datos DAL Opciones TableAdapters Embedded Data Access Code. Separate DAL Classes. (Mejores prácticas) La última es la seleccionada e implica separadas DAL para cada uno de sus objetos de negocio: clase ContactPersonManager tiene a un colega ContactPersonDB, EmailAddressManager tiene una clase EmailAddressDB, etcétera. Las clase *DB interactúan directamente con la base de datos, con sus métodos CRUD. Los parámetros son los objetos del negocio (ContactPerson, EmailAddress, etc) o tipos simples según el caso.
Diseño del DAL En todas las clases el método Delete() retorna un bool para establecer el éxito o fracaso de la operación. El método Save() devuelve un int que sostiene el ID del registro que fue insertado o actualizado por el método. GetItem() y GetList() retornan objetos del negocio.
Diseño Separación lógica de Capas con Namespaces Para separar claramente los objetos de negocio, la lógica de negocio y el acceso de datos, se ponen todos ellos en namespaces separados: Spaanjaars.ContactManager.BO: para los objetos de negocio. Spaanjaars.ContactManager.Bll: para la lógica de negocio. Spaanjaars.ContactManager.Dal: para los datos tienen acceso al código. Tanto la capa lógica de negocio como la capa de acceso de datos consiguen una referencia a los objetos en el BO namespace. Además, la capa de negocio consigue una referencia a la capa de acceso de datos para toda la interacción de datos. Los objetos del negocio se colocan en una capa diferente para evitar referencias circulares entre la capa del negocio y la de datos.
Diseño de la Base de Datos Es conveniente usar una CASE. Seguir los pasos de un diseño adecuado de Base de datos. Las clases del negocio se mapean a tablas con sus respectivas relaciones. Desarrollar Procedimientos Almacenados adecuados a ser llamados por las clases *DB, teniendo en cuenta las interfaces definidas. Opcional: Usar un proyecto de BD.
Diseño Final La PL, por ejemplo una página de ASPX llamada ShowContactPerson.aspx, pide el BLL algún objeto, por ejemplo una persona de contacto. A través de un llamdo a ContactPersonManager. GetItem () obteniendo el ID de la persona de contacto. El BLL opcionalmente puede realizar alguna validación y luego envíar la petición al DAL llamando ContactManagerDB.GetItem (). El DAL se conecta a la base de datos y pide un registro específico. Ejecutando un procedimiento almacenado, como sprocContactPersonSelectSingleItem y pasándolo el ID de la persona de contacto. Cuando el registro es encontrado, es devuelto de la base de datos al DAL en un objeto de SqlDataReader. El DAL empaqueta los datos de base de datos en un ejemplar objeto ContactPerson el del BO y lo devuelve al BLL. Finalmente, el BLL devuelve el objeto de ContactPerson a la capa de Presentación, donde podría ser mostrado sobre una página Web por ejemplo.
Implementación de la Aplicación Clases, propiedades, código de métodos, conexión a la BD, separación física de capas
Creación de Carpetas y Archivos de Clase Separa físicamente las capas: BusinessLogic: Almacena archivos de la capa o namespace BLL. BusinessObject: Archivos de la capa o namespace BO. Contiene las Colecciones. DataAccess: Archivos de la capa o namespace Dal. Enums: Las Enumeraciones.
Crear Clases BO - Mejores Prácticas Utilizar el concepto de #Region, para separar secciones de código. Especialmente establecer variables privadas y públicas. Conjuntos de métodos específicos.
Crear Clases BO - Mejores Prácticas Correcto uso acceso a las variables privadas a través de propiedades. Es importante crear una propiedad type que retorne o asigne valores de los tipo enumerados a las propiedades de clase.
Crear Clases BLL Cada método de esta capa tiene la siguiente forma: Una de la funcionalidades es validar RN: public static EmailAddress GetItem(int id) { return EmailAddressDB.GetItem(id); } public static EmailAddress GetItem(int id, IPrincipal currentUser) { if (!currentUser.IsInRole("EmailAddressManagers")) throw new NotSupportedException(@"You're not allowed to call EmailAddressManager.GetItem when you're not in the EmailAddressManagers role"); } return EmailAddressDB.GetItem(id); Opcionalmente se pueden cachear los objetos devueltos para no acceder nuevamente a la BD. EmailAddress myEmailAddress = EmailAddressManager.GetItem(10, Context.User);
Crear las Clases de DAL Public Shared Function GetItem(ByVal id As Integer) As Address Dim myAddress As Address = Nothing Dim myConnection As SqlConnection = New SqlConnection(AppConfiguration.ConnectionString) Try Dim myCommand As SqlCommand = New SqlCommand("sprocAddressSelectSingleItem", myConnection) myCommand.CommandType = CommandType.StoredProcedure myCommand.Parameters.AddWithValue("@id", id) myConnection.Open Dim myReader As SqlDataReader = myCommand.ExecuteReader If myReader.Read Then myAddress = FillDataRecord(myReader) End If myReader.Close Finally CType(myReader, IDisposable).Dispose() End Try myConnection.Close CType(myConnection, IDisposable).Dispose() Return myAddress End Function
Crear las Clases de DAL Private Shared Function FillDataRecord(ByVal myDataRecord As IDataRecord) As EmailAddress Dim myEmailAddress As EmailAddress = New EmailAddress myEmailAddress.Id = myDataRecord.GetInt32(myDataRecord.GetOrdinal("Id")) myEmailAddress.Email = myDataRecord.GetString(myDataRecord.GetOrdinal("Email")) myEmailAddress.Type = CType(myDataRecord.GetInt32(myDataRecord.GetOrdinal("EmailType")), ContactType) myEmailAddress.ContactPersonId = myDataRecord.GetInt32(myDataRecord.GetOrdinal("ContactPersonId")) Return myEmailAddress End Function ‘la interfaz IDataRecord da un acceso fuertemente tipado a los valores ‘dentro de cada registro. Permite independizar el procedimiento del ‘proveedor de datos Ej. SqlDataReader, OleDbDataReader. Para el manejo de Nulos se aconseja lo siguiente: if (!myDataRecord.IsDBNull(myDataRecord.GetOrdinal("ZipCode"))) { myAddress.ZipCode = myDataRecord.GetString(myDataRecord.GetOrdinal("ZipCode")); }
Crear las Clases de DAL Public Shared Function GetList(ByVal contactPersonId As Integer) As EmailAddressList Dim tempList As EmailAddressList = Nothing Dim myConnection As SqlConnection = New SqlConnection(AppConfiguration.ConnectionString) Try Dim myCommand As SqlCommand = New SqlCommand("sprocEmailAddressSelectList", myConnection) myCommand.CommandType = CommandType.StoredProcedure myCommand.Parameters.AddWithValue("@contactPersonId", contactPersonId) myConnection.Open() Dim myReader As SqlDataReader = myCommand.ExecuteReader If myReader.HasRows Then tempList = New EmailAddressList While myReader.Read tempList.Add(FillDataRecord(myReader)) End While End If myReader.Close() Finally CType(myReader, IDisposable).Dispose() End Try CType(myConnection, IDisposable).Dispose() Return tempList End Function Para el manejo de Nulos se aconseja lo siguiente: if (!myDataRecord.IsDBNull(myDataRecord.GetOrdinal("ZipCode"))) { myAddress.ZipCode = myDataRecord.GetString(myDataRecord.GetOrdinal("ZipCode")); }
Creación de Clases de Listas de Objetos de Negocio Imports System Imports System.Collections.Generic namespace Spaanjaars.ContactManager.BO ''' <summary> ''' The EmailAddressList class is designed to work with lists of instances of EmailAddress, hacer su uso tipado y mas fácil de usar. ''' </summary> Public Class EmailAddressList Inherits List(Of EmailAddress) Public Sub New() End Sub End Class End Namespace ‘El llamado en la funcion GetList seria: ‘Dim tempList As EmailAddressList = Nothing
Creación de Procedimientos Almacenados en la BD CREATE PROCEDURE sprocEmailAddressSelectSingleItem @id int AS SELECT Id, Email, EmailType, ContactPersonId FROM EmailAddress WHERE Id = @id
Clases DAL Método Save() Public Shared Function Save(ByVal myEmailAddress As EmailAddress) As Integer Dim result As Integer = 0 Dim myConnection As SqlConnection = New SqlConnection(AppConfiguration.ConnectionString) Try Dim myCommand As SqlCommand = New SqlCommand("sprocEmailAddressInsertUpdateSingleItem", myConnection) myCommand.CommandType = CommandType.StoredProcedure If myEmailAddress.Id = -1 Then myCommand.Parameters.AddWithValue("@id", DBNull.Value) Else myCommand.Parameters.AddWithValue("@id", myEmailAddress.Id) End If myCommand.Parameters.AddWithValue("@email", myEmailAddress.Email) myCommand.Parameters.AddWithValue("@emailType", myEmailAddress.Type) myCommand.Parameters.AddWithValue("@contactPersonId", myEmailAddress.ContactPersonId) Dim returnValue As DbParameter returnValue = myCommand.CreateParameter returnValue.Direction = ParameterDirection.ReturnValue myCommand.Parameters.Add(returnValue) myConnection.Open() myCommand.ExecuteNonQuery() result = Convert.ToInt32(returnValue.Value) myConnection.Close() Finally CType(myConnection, IDisposable).Dispose() End Try Return result End Function
Creación de Procedimientos Almacenados en la BD CREATE PROCEDURE sprocEmailAddressInsertUpdateSingleItem @id int, @email nvarchar (100), @emailType int, @contactPersonId int AS DECLARE @ReturnValue int IF (@id IS NULL) -- New Item BEGIN INSERT INTO EmailAddress ( Email, EmailType, ContactPersonId ) VALUES @email, @emailType, @contactPersonId SELECT @ReturnValue = SCOPE_IDENTITY() END ELSE BEGIN UPDATE EmailAddress SET Email = @email, EmailType = @emailType, ContactPersonId = @contactPersonId WHERE Id = @id SELECT @ReturnValue = @id IF (@@ERROR != 0) RETURN -1 RETURN @ReturnValue GO
Elementos de Implementación de la clase ContactPerson Private _strAddresses As AddressList = New AddressList Private _phoneNumbers As PhoneNumberList = New PhoneNumberList Private _emailAddresses As EmailAddressList = New EmailAddressList Public ReadOnly Property FullName() As String Get Dim tempValue As String = _strFirstName If Not String.IsNullOrEmpty(_stMiddleName) Then tempValue += " " + MiddleName End If tempValue += " " + _strLastName Return tempValue End Get End Property
Elementos de Implementación de la clase ContactPerson Public Shared Function GetItem(ByVal id As Integer) As ContactPerson Return GetItem(id, False) End Function Public Shared Function GetItem(ByVal id As Integer, ByVal getContactRecords As Boolean) As ContactPerson Dim myContactPerson As ContactPerson = ContactPersonDB.GetItem(id) If Not (myContactPerson Is Nothing) AndAlso getContactRecords Then myContactPerson.Addresses = AddressDB.GetList(id) myContactPerson.EmailAddresses = EmailAddressDB.GetList(id) myContactPerson.PhoneNumbers = PhoneNumberDB.GetList(id) End If Return myContactPerson
Elementos de Implementación de la clase ContactPerson Public Shared Function Save(ByVal myContactPerson As ContactPerson) As Integer Dim myTransactionScope As TransactionScope = New TransactionScope Try Dim contactPersonId As Integer = ContactPersonDB.Save(myContactPerson) For Each myAddress As Address In myContactPerson.Addresses myAddress.ContactPersonId = contactPersonId AddressDB.Save(myAddress) Next For Each myEmailAddress As EmailAddress In myContactPerson.EmailAddresses myEmailAddress.ContactPersonId = contactPersonId EmailAddressDB.Save(myEmailAddress) For Each myPhoneNumber As PhoneNumber In myContactPerson.PhoneNumbers myPhoneNumber.ContactPersonId = contactPersonId PhoneNumberDB.Save(myPhoneNumber) myContactPerson.Id = contactPersonId myTransactionScope.Complete Return contactPersonId Finally CType(myTransactionScope, IDisposable).Dispose() End Try End Function
Elementos de Implementación de la clase ContactPerson CREATE PROCEDURE sprocContactPersonDeleteSingleItem @id int AS BEGIN TRAN DELETE FROM Address WHERE ContactPersonId = @id IF @@ERROR <> 0 BEGIN ROLLBACK TRAN RETURN -1 END EmailAddress DELETE FROM PhoneNumber WHERE ContactPersonId = @id IF @@ERROR <> 0 BEGIN ROLLBACK TRAN RETURN -1 END ContactPerson Id = @id COMMIT TRAN
Uso de la clase ContactPerson ContactPerson myContactPerson = new ContactPerson(); myContactPerson.FirstName = "Imar"; myContactPerson.LastName = "Spaanjaars"; myContactPerson.DateOfBirth = new DateTime(1971, 8, 9); myContactPerson.Type = PersonType.Family; Address myAdress = new Address(); myAdress.Street = "Some Street"; myAdress.HouseNumber = "Some Number"; myAdress.ZipCode = "Some Zip"; myAdress.City = "Some City"; myAdress.Country = "Some Country"; myContactPerson.Addresses.Add(myAdress); EmailAddress myEmailAdress = new EmailAddress(); myEmailAdress.Email = "Imar@DoNotSpam.com"; myEmailAdress.Type = ContactType.Personal; myContactPerson.EmailAddresses.Add(myEmailAdress); PhoneNumber myPhoneNumber = new PhoneNumber(); myPhoneNumber.Number = "555 - 2368"; myPhoneNumber.Type = ContactType.Personal; myContactPerson.PhoneNumbers.Add(myPhoneNumber); ContactPersonManager.Save(myContactPerson);
Implementación de la Aplicación Web - DAL Web Forms y controles, Web Config.
Resumen de la Capas
Archivos de la Aplicación Web App_Data and NLayer.mdf: BD Usada en la aplicación. App_Themes and Css\Styles.css: Contirne un tema para el gridView. Y el archivo de estilos. Web.config: Configuraciones de la aplicación.
Formulario Default.aspx Cree una nueva página e intercambie a la vista de diseño. Añada un GridView a la página. Abra el panel de Tareas del GridView y Escoge la Fuente de Datos select <New data source>. En el asistente de configuración del DataSource, Seleccione el Objeto y click OK. Escoja un Objeto De negocio, asegúrese muestre sólo los componentes de datos esten chequeados y luego escoger el objeto apropiado de negocio de la lista. En el caso, escoger: Spaanjaars. ContactManager. Bll. ContactPersonManager: Define Data Methods window asegúrese que sobre la etiqueta escogida sea el GetList(). Después, limpie la selección sobre la etiqueta de Update, esta página no necesita un método de Update. Dejar las demás etiquetas como están.
Atributos para el Diseñador <DataObjectMethod> establece el método por defecto para traer datos del AddressList. <DataObjectFieldAttribute(True, True, False)> Permite que la propiedad la use el asistente. <DataObjectMethod(DataObjectMethodType.Select, True)> _ Public Shared Function GetList(ByVal contactPersonId As Integer) As AddressList Return AddressDB.GetList(contactPersonId) End Function <DataObjectFieldAttribute(True, True, False)> _ Public Property Id() As Integer Get Return _intId End Get Set(ByVal value As Integer) _intId = Value End Set End Property
Vista de Presentación Todos los contactos
Añadir botones para permitir al usuario nuevo, editar, borrar <asp:ButtonField CommandName="Edit" Text="Edit"/> <asp:TemplateField ShowHeader="False"> <ItemTemplate> <asp:LinkButton ID="LinkButton1" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete" OnClientClick="return confirm('Are you sure you want to delete this contact person?');"> </asp:LinkButton> </ItemTemplate> </asp:TemplateField>
Establecer un método que captura el comando seleccionado <asp:GridView ID="gvContactPersons" runat="server" AutoGenerateColumns="False" DataSourceID="odsContactPersons" DataKeyNames="Id" OnRowCommand="gvContactPersons_RowCommand" AllowPaging="True" CellPadding="4" GridLines="None"><Columns> ....</Columns></asp:GridView>
Vista de Edición de Datos