Controles de fonte de dados

pela Microsoft

O controle DataGrid no ASP.NET 1.x marcou uma grande melhoria no acesso a dados em aplicativos Web. No entanto, não foi tão amigável quanto poderia ter sido. Ele ainda exigia uma quantidade considerável de código para obter muita funcionalidade útil dele. Esse é o modelo em todos os esforços de acesso a dados no 1.x.

O controle DataGrid no ASP.NET 1.x marcou uma grande melhoria no acesso a dados em aplicativos Web. No entanto, não foi tão amigável quanto poderia ter sido. Ele ainda exigia uma quantidade considerável de código para obter muita funcionalidade útil dele. Esse é o modelo em todos os esforços de acesso a dados no 1.x.

ASP.NET 2.0 resolve isso com, em parte, com controles de fonte de dados. Os controles de fonte de dados no ASP.NET 2.0 fornecem aos desenvolvedores um modelo declarativo para recuperar dados, exibir dados e editar dados. A finalidade dos controles de fonte de dados é fornecer uma representação consistente de dados para controles associados a dados, independentemente da fonte desses dados. No centro dos controles de fonte de dados no ASP.NET 2.0 está a classe abstrata DataSourceControl. A classe DataSourceControl fornece uma implementação base da interface IDataSource e da interface IListSource, a última das quais permite atribuir o controle da fonte de dados como o DataSource de um controle associado a dados (por meio da nova propriedade DataSourceId discutida posteriormente) e expor os dados nela como uma lista. Cada lista de dados de um controle de fonte de dados é exposta como um objeto DataSourceView. O acesso às instâncias do DataSourceView é fornecido pela interface IDataSource. Por exemplo, o método GetViewNames retorna uma ICollection que permite enumerar os DataSourceViews associados a um controle de fonte de dados específico e o método GetView permite que você acesse uma instância específica do DataSourceView por nome.

Os controles de fonte de dados não têm interface do usuário. Eles são implementados como controles de servidor para que possam dar suporte à sintaxe declarativa e para que tenham acesso ao estado da página, se desejado. Os controles de fonte de dados não renderizam nenhuma marcação HTML para o cliente.

Observação

Como você verá posteriormente, também há benefícios de cache obtidos usando controles de fonte de dados.

Armazenando cadeias de conexão

Antes de analisarmos como configurar controles de fonte de dados, devemos abordar um novo recurso no ASP.NET 2.0 sobre cadeias de conexão. ASP.NET 2.0 apresenta uma nova seção no arquivo de configuração que permite armazenar facilmente cadeias de conexão que podem ser lidas dinamicamente em runtime. A <seção connectionStrings> facilita o armazenamento de cadeias de conexão.

O snippet abaixo adiciona uma nova cadeia de conexão.

<connectionStrings> <add name="Northwind" connectionString="Data Source=localhost; Integrated Security=SSPI;Initial Catalog=Northwind;" providerName="System.Data.SqlClient" /> </connectionStrings>

Observação

Assim como acontece com a <seção appSettings> , a <seção connectionStrings> aparece fora da <seção system.web> no arquivo de configuração.

Para usar essa cadeia de conexão, você pode usar a sintaxe a seguir ao definir o atributo ConnectionString de um controle de servidor.

ConnectionString="<%$ ConnectionStrings:Northwind%>"

A <seção connectionStrings> também pode ser criptografada para que informações confidenciais não sejam expostas. Essa capacidade será abordada em um módulo posterior.

Armazenar em cache fontes de dados

Cada DataSourceControl fornece quatro propriedades para configurar o cache; EnableCaching, CacheDuration, CacheExpirationPolicy e CacheKeyDependency.

Enablecaching

EnableCaching é uma propriedade booliana que determina se o cache está habilitado ou não para o controle da fonte de dados.

Propriedade CacheDuration

A propriedade CacheDuration define o número de segundos que o cache permanece válido. Definir essa propriedade como 0 faz com que o cache permaneça válido até que seja invalidado explicitamente.

Propriedade CacheExpirationPolicy

A propriedade CacheExpirationPolicy pode ser definida como Absoluto ou Deslizante. Defini-lo como Absoluto significa que a quantidade máxima de tempo que os dados serão armazenados em cache é o número de segundos especificado pela propriedade CacheDuration. Definindo-o como Deslizante, o tempo de expiração é redefinido quando cada operação é executada.

Propriedade CacheKeyDependency

Se um valor de cadeia de caracteres for especificado para a propriedade CacheKeyDependency, ASP.NET configurará uma nova dependência de cache com base nessa cadeia de caracteres. Isso permite invalidar explicitamente o cache simplesmente alterando ou removendo o CacheKeyDependency.

Importante: se a representação estiver habilitada e o acesso à fonte de dados e/ou o conteúdo dos dados forem baseados na identidade do cliente, é recomendável que o cache seja desabilitado definindo EnableCaching como False. Se o cache estiver habilitado nesse cenário e um usuário diferente do usuário que solicitou originalmente os dados emitir uma solicitação, a autorização para a fonte de dados não será imposta. Os dados serão simplesmente fornecidos do cache.

O controle SqlDataSource

O controle SqlDataSource permite que um desenvolvedor acesse dados armazenados em qualquer banco de dados relacional que dê suporte a ADO.NET. Ele pode usar o provedor System.Data.SqlClient para acessar um banco de dados SQL Server, o provedor System.Data.OleDb, o provedor System.Data.Odbc ou o provedor System.Data.OracleClient para acessar o Oracle. Portanto, o SqlDataSource certamente não é usado apenas para acessar dados em um banco de dados SQL Server.

Para usar o SqlDataSource, basta fornecer um valor para a propriedade ConnectionString e especificar um comando SQL ou procedimento armazenado. O controle SqlDataSource cuida de trabalhar com a arquitetura de ADO.NET subjacente. Ele abre a conexão, consulta a fonte de dados ou executa o procedimento armazenado, retorna os dados e fecha a conexão para você.

Observação

Como a classe DataSourceControl fecha automaticamente a conexão para você, ela deve reduzir o número de chamadas de clientes geradas pelo vazamento de conexões de banco de dados.

O snippet de código abaixo associa um controle DropDownList a um controle SqlDataSource usando a cadeia de conexão armazenada no arquivo de configuração, conforme mostrado acima.

<asp:SqlDataSource id="SqlDataSource1" runat="server" DataSourceMode="DataReader" ConnectionString="<%$ ConnectionStrings:Northwind%>" SelectCommand="SELECT EmployeeID, LastName FROM Employees"> </asp:SqlDataSource><asp:DropDownList id="ListBox1" runat="server" DataTextField="LastName" DataValueField="EmployeeID" DataSourceID="SqlDataSource1"> </asp:DropDownList>

Conforme ilustrado acima, a propriedade DataSourceMode do SqlDataSource especifica o modo para a fonte de dados. No exemplo acima, o DataSourceMode é definido como DataReader. Nesse caso, o SqlDataSource retornará um objeto IDataReader usando um cursor somente encaminhamento e somente leitura. O tipo especificado de objeto retornado é controlado pelo provedor usado. Nesse caso, estou usando o provedor System.Data.SqlClient, conforme especificado na <seção connectionStrings> do arquivo web.config. Portanto, o objeto retornado será do tipo SqlDataReader. Ao especificar um valor DataSourceMode de DataSet, os dados podem ser armazenados em um DataSet no servidor. Esse modo permite que você adicione recursos como classificação, paginação etc. Se eu tivesse associado dados do SqlDataSource a um controle GridView, teria escolhido o modo DataSet. No entanto, no caso de um DropDownList, o modo DataReader é a escolha correta.

Observação

Ao armazenar em cache um SqlDataSource ou um AccessDataSource, a propriedade DataSourceMode deve ser definida como DataSet. Uma exceção ocorrerá se você habilitar o cache com um DataSourceMode do DataReader.

Propriedades do SqlDataSource

Veja a seguir algumas das propriedades do controle SqlDataSource.

Cancelselectonnullparameter

Um valor booliano que especifica se um comando select será cancelado se um dos parâmetros for nulo. True por padrão.

Conflictdetection

Em uma situação em que vários usuários podem estar atualizando uma fonte de dados ao mesmo tempo, a propriedade ConflictDetection determina o comportamento do controle SqlDataSource. Essa propriedade é avaliada como um dos valores da enumeração ConflictOptions. Esses valores são CompareAllValues e OverwriteChanges. Se definido como OverwriteChanges, a última pessoa a gravar dados na fonte de dados substituirá as alterações anteriores. No entanto, se a propriedade ConflictDetection estiver definida como CompareAllValues, os parâmetros serão criados para as colunas retornadas pelo SelectCommand e os parâmetros também serão criados para manter os valores originais em cada uma dessas colunas, permitindo que o SqlDataSource determine se os valores foram alterados ou não desde que SelectCommand foi executado.

Deletecommand

Define ou obtém a cadeia de caracteres SQL usada ao excluir linhas do banco de dados. Isso pode ser uma consulta SQL ou um nome de procedimento armazenado.

Deletecommandtype

Define ou obtém o tipo de comando delete, uma consulta SQL (Texto) ou um procedimento armazenado (StoredProcedure).

Deleteparameters

Retorna os parâmetros usados pelo DeleteCommand do objeto SqlDataSourceView associado ao controle SqlDataSource.

Oldvaluesparameterformatstring

Essa propriedade é usada para especificar o formato dos parâmetros de valor originais nos casos em que a propriedade ConflictDetection é definida como CompareAllValues. O padrão é {0} o que significa que os parâmetros de valor originais terão o mesmo nome que o parâmetro original. Em outras palavras, se o nome do campo for EmployeeID, o parâmetro de valor original será @EmployeeID.

SelectCommand

Define ou obtém a cadeia de caracteres SQL usada para recuperar dados do banco de dados. Isso pode ser uma consulta SQL ou um nome de procedimento armazenado.

Selectcommandtype

Define ou obtém o tipo de comando select, uma consulta SQL (Texto) ou um procedimento armazenado (StoredProcedure).

Selectparameters

Retorna os parâmetros usados pelo SelectCommand do objeto SqlDataSourceView associado ao controle SqlDataSource.

Sortparametername

Obtém ou define o nome de um parâmetro de procedimento armazenado usado ao classificar dados recuperados pelo controle da fonte de dados. Válido somente quando SelectCommandType é definido como StoredProcedure.

Sqlcachedependency

Uma cadeia de caracteres delimitada por ponto e vírgula que especifica os bancos de dados e tabelas usados em uma dependência de cache SQL Server. (As dependências do cache SQL serão discutidas em um módulo posterior.)

Updatecommand

Define ou obtém a cadeia de caracteres SQL usada ao atualizar dados no banco de dados. Isso pode ser uma consulta SQL ou um nome de procedimento armazenado.

Updatecommandtype

Define ou obtém o tipo de comando de atualização, uma consulta SQL (Texto) ou um procedimento armazenado (StoredProcedure).

Updateparameters

Retorna os parâmetros usados pelo UpdateCommand do objeto SqlDataSourceView associado ao controle SqlDataSource.

O controle AccessDataSource

O controle AccessDataSource deriva da classe SqlDataSource e é usado para associar dados a um banco de dados do Microsoft Access. A propriedade ConnectionString para o controle AccessDataSource é uma propriedade somente leitura. Em vez de usar a propriedade ConnectionString, a propriedade DataFile é usada para apontar para o Banco de Dados do Access, conforme mostrado abaixo.

<asp:AccessDataSource id="AccessDataSource1" runat="server" DataFile="~/App_Data/Northwind.mdb"> </asp:AccessDataSource>

O AccessDataSource sempre definirá o ProviderName do SqlDataSource base como System.Data.OleDb e se conectará ao banco de dados usando o provedor OLE DB Microsoft.Jet.OLEDB.4.0. Você não pode usar o controle AccessDataSource para se conectar a um banco de dados do Access protegido por senha. Se você precisar se conectar a um banco de dados protegido por senha, deverá usar o controle SqlDataSource.

Observação

Os bancos de dados de acesso armazenados no site devem ser colocados no diretório App_Data. ASP.NET não permite que os arquivos neste diretório sejam navegados. Você precisará conceder permissões de Leitura e Gravação da conta de processo ao diretório App_Data ao usar bancos de dados do Access.

O controle XmlDataSource

O XmlDataSource é usado para associar dados XML a controles associados a dados. Você pode associar a um arquivo XML usando a propriedade DataFile ou pode associar a uma cadeia de caracteres XML usando a propriedade Data. O XmlDataSource expõe atributos XML como campos associáveis. Nos casos em que você precisa associar a valores que não são representados como atributos, você precisará usar uma transformação XSL. Você também pode usar expressões XPath para filtrar dados XML.

Considere o seguinte arquivo XML:

<?xml version="1.0" encoding="utf-8" ?> <People> <Person FirstName="Jake" LastName="Stone"> <Address> <Street>345 Maple St.</Street> <City>Redmond</City> <Region>WA</Region> <ZipCode>01434</ZipCode> </Address> <Job> <Title>CEO</Title> <Description>Develops company strategies.</Description> </Job> </Person> <Person FirstName="Jacob" LastName="Ladder"> <Address> <Street>123 Elm St.</Street> <City>Seattle</City> <Region>WA</Region> <ZipCode>11223</ZipCode> </Address> <Job> <Title>Attorney</Title> <Description>Reviews legal issues.</Description> </Job> </Person> <Person FirstName="Angela" LastName="Hound"> <Address> <Street>34 Palm Avenue</Street> <City>Renton</City> <Region>WA</Region> <ZipCode>63910</ZipCode> </Address> <Job> <Title>IT Director</Title> <Description>In charge of corporate network.</Description> </Job> </Person> </People>

Observe que o XmlDataSource usa uma propriedade XPath de Pessoas/Person para filtrar apenas os <nós de Pessoa>. Em seguida, o DropDownList associa dados ao atributo LastName usando a propriedade DataTextField.

Embora o controle XmlDataSource seja usado principalmente para associar dados a dados XML somente leitura, é possível editar o arquivo de dados XML. Observe que, nesses casos, a inserção automática, a atualização e a exclusão de informações no arquivo XML não acontecem automaticamente como acontece com outros controles de fonte de dados. Em vez disso, você precisará escrever código para editar manualmente os dados usando os seguintes métodos do controle XmlDataSource.

Getxmldocument

Recupera um objeto XmlDocument que contém o código XML recuperado pelo XmlDataSource.

Salvar

Salva o XmlDocument na memória de volta na fonte de dados.

É importante perceber que o método Save só funcionará quando as duas condições a seguir forem atendidas:

  1. O XmlDataSource está usando a propriedade DataFile para associar a um arquivo XML em vez da propriedade Data para associar a dados XML na memória.
  2. Nenhuma transformação é especificada por meio da propriedade Transform ou TransformFile.

Observe também que o método Save pode produzir resultados inesperados quando chamado por vários usuários simultaneamente.

O controle ObjectDataSource

Os controles de fonte de dados que abordamos até este ponto são excelentes opções para aplicativos de duas camadas em que o controle da fonte de dados se comunica diretamente com o armazenamento de dados. No entanto, muitos aplicativos do mundo real são aplicativos de várias camadas em que um controle de fonte de dados pode precisar se comunicar com um objeto de negócios que, por sua vez, se comunica com a camada de dados. Nessas situações, o ObjectDataSource preenche bem a fatura. O ObjectDataSource funciona em conjunto com um objeto de origem. O controle ObjectDataSource criará uma instância do objeto de origem, chamará o método especificado e descartará a instância do objeto dentro do escopo de uma única solicitação, se o objeto tiver métodos de instância em vez de métodos estáticos (compartilhados no Visual Basic). Portanto, seu objeto deve ser sem estado. Ou seja, seu objeto deve adquirir e liberar todos os recursos necessários dentro do intervalo de uma única solicitação. Você pode controlar como o objeto de origem é criado manipulando o evento ObjectCreating do controle ObjectDataSource. Você pode criar uma instância do objeto de origem e definir a propriedade ObjectInstance da classe ObjectDataSourceEventArgs para essa instância. O controle ObjectDataSource usará a instância criada no evento ObjectCreating em vez de criar uma instância por conta própria.

Se o objeto de origem de um controle ObjectDataSource expor métodos estáticos públicos (compartilhados no Visual Basic) que podem ser chamados para recuperar e modificar dados, um controle ObjectDataSource chamará esses métodos diretamente. Se um controle ObjectDataSource precisar criar uma instância do objeto de origem para fazer chamadas de método, o objeto deverá incluir um construtor público que não usa parâmetros. O controle ObjectDataSource chamará esse construtor quando ele criar uma nova instância do objeto de origem.

Se o objeto de origem não contiver um construtor público sem parâmetros, você poderá criar uma instância do objeto de origem que será usada pelo controle ObjectDataSource no evento ObjectCreating.

Especificando métodos de objeto

O objeto de origem de um controle ObjectDataSource pode conter qualquer número de métodos usados para selecionar, inserir, atualizar ou excluir dados. Esses métodos são chamados pelo controle ObjectDataSource com base no nome do método, conforme identificado usando a propriedade SelectMethod, InsertMethod, UpdateMethod ou DeleteMethod do controle ObjectDataSource. O objeto de origem também pode incluir um método SelectCount opcional, que é identificado pelo controle ObjectDataSource usando a propriedade SelectCountMethod, que retorna a contagem do número total de objetos na fonte de dados. O controle ObjectDataSource chamará o método SelectCount depois que um método Select for chamado para recuperar o número total de registros na fonte de dados para uso durante a paginação.

Laboratório usando controles de fonte de dados

Exercício 1 – Exibindo dados com o controle SqlDataSource

O exercício a seguir usa o controle SqlDataSource para se conectar ao banco de dados Northwind. Ele pressupõe que você tenha acesso ao banco de dados Northwind em uma instância do SQL Server 2000.

  1. Criar um site do ASP.NET.

  2. Adicione um novo arquivo de web.config.

    1. Clique com o botão direito do mouse no projeto no Gerenciador de Soluções e clique em Adicionar Novo Item.
    2. Escolha Arquivo de Configuração da Web na lista de modelos e clique em Adicionar.
  3. Edite a <seção connectionStrings> da seguinte maneira:

    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
        ConnectionString="<%$ConnectionStrings:Northwind%>"
        SelectCommand="SELECT * FROM Products">
    </asp:SqlDataSource>
    
  4. Alterne para Exibição de código e adicione um atributo ConnectionString e um atributo SelectCommand ao controle asp <:SqlDataSource> da seguinte maneira:

    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
        ConnectionString="<%$ConnectionStrings:Northwind%>"
        SelectCommand="SELECT * FROM Products">
    </asp:SqlDataSource>
    
  5. Na exibição Design, adicione um novo controle GridView.

  6. Na lista suspensa Escolher Fonte de Dados no menu Tarefas do GridView, escolha SqlDataSource1.

  7. Clique com o botão direito do mouse em Default.aspx e escolha Exibir no Navegador no menu. Clique em Sim quando for solicitado a salvar.

  8. O GridView exibe os dados da tabela Produtos.

Exercício 2 – Editando dados com o controle SqlDataSource

O exercício a seguir demonstra como associar dados a um controle DropDownList usando a sintaxe declarativa e permite editar os dados apresentados no controle DropDownList.

  1. No modo design, exclua o controle GridView de Default.aspx.

    Importante: deixe o controle SqlDataSource na página.

  2. Adicione um controle DropDownList a Default.aspx.

  3. Alterne para Exibição de origem.

  4. Adicione um atributo DataSourceId, DataTextField e DataValueField ao <controle asp:DropDownList> da seguinte maneira:

    <asp:DropDownList ID="ddlProducts" runat="server"
         DataSourceId="SqlDataSource1" DataTextField="ProductName"
         DataValueField="ProductID">
    </asp:DropDownList>
    
  5. Salve Default.aspx e exiba-o no navegador. Observe que o DropDownList contém todos os produtos do banco de dados Northwind.

  6. Feche o navegador.

  7. No modo de exibição Origem de Default.aspx, adicione um novo controle TextBox abaixo do controle DropDownList. Altere a propriedade ID da TextBox para txtProductName.

  8. No controle TextBox, adicione um novo controle Button. Altere a propriedade ID do Botão para btnUpdate e a propriedade Text para Atualizar Nome do Produto.

  9. No modo de exibição De origem de Default.aspx, adicione uma propriedade UpdateCommand e dois novos UpdateParameters à marca SqlDataSource da seguinte maneira:

    <asp:SqlDataSource ID="SqlDataSource1" runat="server"
        ConnectionString="<%$ConnectionStrings:Northwind%>"
        SelectCommand="SELECT * FROM Products"
        UpdateCommand="UPDATE Products SET ProductName=@ProductName WHERE ProductID=@ProductID">
          <UpdateParameters>
          <asp:ControlParameter Name="ProductName" 
            ControlID="txtProductName" PropertyName="Text" />
          <asp:ControlParameter Name="ProductID" 
            ControlID="ddlProducts" PropertyName="SelectedValue" />
    </asp:SqlDataSource>
    

    Observação

    Observe que há dois parâmetros de atualização (ProductName e ProductID) adicionados neste código. Esses parâmetros são mapeados para a propriedade Text do txtProductName TextBox e a propriedade SelectedValue do ddlProducts DropDownList.

  10. Alterne para o modo design e clique duas vezes no controle Botão para adicionar um manipulador de eventos.

  11. Adicione o seguinte código ao código btnUpdate_Click:

    SqlDataSource1.Update();
    
  12. Clique com o botão direito do mouse em Default.aspx e escolha exibi-lo no navegador. Clique em Sim quando solicitado a salvar todas as alterações.

  13. ASP.NET classes parciais 2.0 permitem compilação em runtime. Não é necessário criar um aplicativo para que as alterações de código entrem em vigor.

  14. Selecione um produto no DropDownList.

  15. Insira um novo nome para o produto selecionado na Caixa de Texto e clique no botão Atualizar.

  16. O nome do produto é atualizado no banco de dados.

Exercício 3 usando o controle ObjectDataSource

Este exercício demonstrará como usar o controle ObjectDataSource e um objeto de origem para interagir com o banco de dados Northwind.

  1. Clique com o botão direito do mouse no projeto em Gerenciador de Soluções e clique em Adicionar Novo Item.

  2. Selecione Web Form na lista de modelos. Altere o nome para object.aspx e clique em Adicionar.

  3. Clique com o botão direito do mouse no projeto em Gerenciador de Soluções e clique em Adicionar Novo Item.

  4. Selecione Classe na lista de modelos. Altere o nome da classe para NorthwindData.cs e clique em Adicionar.

  5. Clique em Sim quando solicitado a adicionar a classe à pasta App_Code.

  6. Adicione o seguinte código ao arquivo NorthwindData.cs:

    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using System.Web.UI.HtmlControls;
    using System.Data.SqlClient;
    public class NorthwindData {
        private string _connectionString;
        public NorthwindData() {
            Initialize();
        }
    
        private void Initialize() {
            if (ConfigurationManager.ConnectionStrings["Northwind"] == null ||
                ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString.Trim() == "") {
                    throw new Exception("A connection string named 'Northwind' with " +
                    "a valid connection string must exist in the <connectionStrings> " +
                    "configuration section for the application.");
            }
            _connectionString = ConfigurationManager.ConnectionStrings["Northwind"].ConnectionString;
        }
    
        public DataTable GetAllEmployees(string sortColumns, int startRecord, int maxRecords) {
            VerifySortColumns(sortColumns);
            string sqlCmd = "SELECT EmployeeID, LastName, FirstName, Address, " +
                "City, Region, PostalCode FROM Employees ";
            if (sortColumns.Trim() == "")
                sqlCmd += "ORDER BY EmployeeID";
            else
                sqlCmd += "ORDER BY " + sortColumns;
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlDataAdapter da = new SqlDataAdapter(sqlCmd, conn);
            DataSet ds = new DataSet();
            try {
                conn.Open();
                da.Fill(ds, startRecord, maxRecords, "Employees");
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
            return ds.Tables["Employees"];
        }
    
        public int SelectCount() {
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand("SELECT COUNT(*) FROM Employees", conn);
            int result = 0;
    
            try {
                conn.Open();
                result = (int)cmd.ExecuteScalar();
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
            return result;
        }
    
        //////////
        // Verify that only valid columns are specified in the sort expression to
        // avoid a SQL Injection attack.
        private void VerifySortColumns(string sortColumns) {
            if (sortColumns.ToLowerInvariant().EndsWith(" desc"))
                sortColumns = sortColumns.Substring(0, sortColumns.Length - 5);
            string[] columnNames = sortColumns.Split(',');
            foreach (string columnName in columnNames) {
                switch (columnName.Trim().ToLowerInvariant()) {
                    case "employeeid":
                        break;
                    case "lastname":
                        break;
                    case "firstname":
                        break;
                    case "":
                        break;
                    default:
                        throw new ArgumentException("SortColumns contains an " +
                            "invalid column name.");
                        break;
                }
            }
        }
    
        // Select an employee.
        public DataTable GetEmployee(int EmployeeID) {
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlDataAdapter da =
                new SqlDataAdapter("SELECT EmployeeID, LastName, FirstName, " +
                "Address, City, Region, PostalCode " +
                " FROM Employees WHERE EmployeeID = @EmployeeID", conn);
            da.SelectCommand.Parameters.Add("@EmployeeID", SqlDbType.Int).Value = EmployeeID;
            DataSet ds = new DataSet();
            try {
                conn.Open();
                da.Fill(ds, "Employees");
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
    
            return ds.Tables["Employees"];
        }
    
        // Delete the Employee by ID.
        public int DeleteEmployee(int EmployeeID) {
             SqlConnection conn = new SqlConnection(_connectionString);
             SqlCommand cmd = new SqlCommand("DELETE FROM Employees WHERE " +
                 "EmployeeID = @EmployeeID", conn);
             cmd.Parameters.Add("@EmployeeID", SqlDbType.Int).Value = EmployeeID;
             int result = 0;
             try {
                 conn.Open();
                 result = cmd.ExecuteNonQuery();
             } catch (SqlException e) {
                 // Handle exception.
             } finally {
                 conn.Close();
             }
    
             return result;
         }
    
         // Update the Employee by original ID.
         public int UpdateEmployee(int EmployeeID, string LastName, string FirstName,
             string Address, string City, string Region,
             string PostalCode) {
             if (String.IsNullOrEmpty(FirstName))
                 throw new ArgumentException("FirstName cannot be null or an empty string.");
             if (String.IsNullOrEmpty(LastName))
                 throw new ArgumentException("LastName cannot be null or an empty string.");
             if (Address == null) { Address = String.Empty; }
             if (City == null) { City = String.Empty; }
             if (Region == null) { Region = String.Empty; }
             if (PostalCode == null) { PostalCode = String.Empty; }
    
             SqlConnection conn = new SqlConnection(_connectionString);
             SqlCommand cmd = new SqlCommand("UPDATE Employees " +
                 " SET FirstName=@FirstName, " +
                 "LastName=@LastName, " +
                 "Address=@Address, City=@City, " +
                 "Region=@Region, " +
                 "PostalCode=@PostalCode " +
                 "WHERE EmployeeID=@EmployeeID", conn);
             cmd.Parameters.Add("@FirstName", SqlDbType.VarChar, 10).Value = FirstName;
             cmd.Parameters.Add("@LastName", SqlDbType.VarChar, 20).Value = LastName;
             cmd.Parameters.Add("@Address", SqlDbType.VarChar, 60).Value = Address;
             cmd.Parameters.Add("@City", SqlDbType.VarChar, 15).Value = City;
             cmd.Parameters.Add("@Region", SqlDbType.VarChar, 15).Value = Region;
             cmd.Parameters.Add("@PostalCode", SqlDbType.VarChar, 10).Value = PostalCode;
             cmd.Parameters.Add("@EmployeeID", SqlDbType.Int).Value = EmployeeID;
    
             int result = 0;
             try {
                 conn.Open();
                 result = cmd.ExecuteNonQuery();
             } catch (SqlException e) {
                 // Handle exception.
             } finally {
                 conn.Close();
             }
    
             return result;
        }
    
        // Insert an Employee.
        public int InsertEmployee(string LastName, string FirstName,
            string Address, string City, string Region,
            string PostalCode) {
            if (String.IsNullOrEmpty(FirstName))
                throw new ArgumentException("FirstName cannot be null or an empty string.");
            if (String.IsNullOrEmpty(LastName))
                throw new ArgumentException("LastName cannot be null or an empty string.");
            if (Address == null) { Address = String.Empty; }
            if (City == null) { City = String.Empty; }
            if (Region == null) { Region = String.Empty; }
            if (PostalCode == null) { PostalCode = String.Empty; }
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand("INSERT INTO Employees " +
                " (FirstName, LastName, Address, " +
                " City, Region, PostalCode) " +
                " Values(@FirstName, @LastName, " +
                "@Address, @City, @Region, @PostalCode); " +
                "SELECT @EmployeeID = SCOPE_IDENTITY()", conn);
    
            cmd.Parameters.Add("@FirstName", SqlDbType.VarChar, 10).Value = FirstName;
            cmd.Parameters.Add("@LastName", SqlDbType.VarChar, 20).Value = LastName;
            cmd.Parameters.Add("@Address", SqlDbType.VarChar, 60).Value = Address;
            cmd.Parameters.Add("@City", SqlDbType.VarChar, 15).Value = City;
            cmd.Parameters.Add("@Region", SqlDbType.VarChar, 15).Value = Region;
            cmd.Parameters.Add("@PostalCode", SqlDbType.VarChar, 10).Value = PostalCode;
            SqlParameter p = cmd.Parameters.Add("@EmployeeID", SqlDbType.Int);
                p.Direction = ParameterDirection.Output;
            int newEmployeeID = 0;
            try {
                conn.Open();
                cmd.ExecuteNonQuery();
                newEmployeeID = (int)p.Value;
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
    
            return newEmployeeID;
        }
    
        //
        // Methods that support Optimistic Concurrency checks.
        //
        // Delete the Employee by ID.
        public int DeleteEmployee(int original_EmployeeID, string original_LastName,
            string original_FirstName, string original_Address,
            string original_City, string original_Region,
            string original_PostalCode) {
    
            if (String.IsNullOrEmpty(original_FirstName))
                throw new ArgumentException("FirstName cannot be null or an empty string.");
            if (String.IsNullOrEmpty(original_LastName))
                throw new ArgumentException("LastName cannot be null or an empty string.");
            if (original_Address == null) { original_Address = String.Empty; }
            if (original_City == null) { original_City = String.Empty; }
            if (original_Region == null) { original_Region = String.Empty; }
            if (original_PostalCode == null) { original_PostalCode = String.Empty; }
            string sqlCmd = "DELETE FROM Employees WHERE EmployeeID = " + @original_EmployeeID
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand(sqlCmd, conn);
            cmd.Parameters.Add("@original_EmployeeID",
                SqlDbType.Int).Value = original_EmployeeID;
            cmd.Parameters.Add("@original_FirstName",
                SqlDbType.VarChar, 10).Value = original_FirstName;
            cmd.Parameters.Add("@original_LastName",
                SqlDbType.VarChar, 20).Value = original_LastName;
            cmd.Parameters.Add("@original_Address",
                SqlDbType.VarChar, 60).Value = original_Address;
            cmd.Parameters.Add("@original_City",
                SqlDbType.VarChar, 15).Value = original_City;
            cmd.Parameters.Add("@original_Region",
                SqlDbType.VarChar, 15).Value = original_Region;
            cmd.Parameters.Add("@original_PostalCode",
                SqlDbType.VarChar, 10).Value = original_PostalCode;
    
            int result = 0;
            try {
                conn.Open();
                result = cmd.ExecuteNonQuery();
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
    
            return result;
        }
    
        // Update the Employee by original ID.
        public int UpdateEmployee(string LastName, string FirstName,
            string Address, string City, string Region,
            string PostalCode, int original_EmployeeID,
            string original_LastName, string original_FirstName,
            string original_Address, string original_City,
            string original_Region, string original_PostalCode) {
    
            if (String.IsNullOrEmpty(FirstName))
                throw new ArgumentException("FirstName cannot be null or an empty string.");
            if (String.IsNullOrEmpty(LastName))
                throw new ArgumentException("LastName cannot be null or an empty string.");
            if (Address == null) { Address = String.Empty; }
            if (City == null) { City = String.Empty; }
            if (Region == null) { Region = String.Empty; }
            if (PostalCode == null) { PostalCode = String.Empty; }
            if (original_Address == null) { original_Address = String.Empty; }
            if (original_City == null) { original_City = String.Empty; }
            if (original_Region == null) { original_Region = String.Empty; }
            if (original_PostalCode == null) { original_PostalCode = String.Empty; }
    
            string sqlCmd = "UPDATE Employees " +
                " SET FirstName = @FirstName, LastName = @LastName, " +
                " Address = @Address, City = @City, Region = @Region, " +
                " PostalCode = @PostalCode " +
                " WHERE EmployeeID = @original_EmployeeID";
    
            SqlConnection conn = new SqlConnection(_connectionString);
            SqlCommand cmd = new SqlCommand(sqlCmd, conn);
            cmd.Parameters.Add("@FirstName", SqlDbType.VarChar, 10).Value = FirstName;
            cmd.Parameters.Add("@LastName", SqlDbType.VarChar, 20).Value = LastName;
            cmd.Parameters.Add("@Address", SqlDbType.VarChar, 60).Value = Address;
            cmd.Parameters.Add("@City", SqlDbType.VarChar, 15).Value = City;
            cmd.Parameters.Add("@Region", SqlDbType.VarChar, 15).Value = Region;
            cmd.Parameters.Add("@PostalCode", SqlDbType.VarChar, 10).Value = PostalCode;
            cmd.Parameters.Add("@original_EmployeeID",
                SqlDbType.Int).Value = original_EmployeeID;
            cmd.Parameters.Add("@original_FirstName",
                SqlDbType.VarChar, 10).Value = original_FirstName;
            cmd.Parameters.Add("@original_LastName",
                SqlDbType.VarChar, 20).Value = original_LastName;
            cmd.Parameters.Add("@original_Address",
                SqlDbType.VarChar, 60).Value = original_Address;
            cmd.Parameters.Add("@original_City",
                SqlDbType.VarChar, 15).Value = original_City;
            cmd.Parameters.Add("@original_Region",
                SqlDbType.VarChar, 15).Value = original_Region;
            cmd.Parameters.Add("@original_PostalCode",
                SqlDbType.VarChar, 10).Value = original_PostalCode;
    
            int result = 0;
    
            try {
                conn.Open();
                result = cmd.ExecuteNonQuery();
            } catch (SqlException e) {
                // Handle exception.
            } finally {
                conn.Close();
            }
            return result;
        }
    }
    
  7. Adicione o seguinte código à exibição De origem de object.aspx:

    <%@ Page language="C#" %>
    <script RunAt="server">
    void EmployeesDetailsView_ItemInserted(Object sender, DetailsViewInsertedEventArgs e) {
        EmployeesGridView.DataBind();
    }
    
    void EmployeesDetailsView_ItemUpdated(Object sender, DetailsViewUpdatedEventArgs e) {
        EmployeesGridView.DataBind();
    }
    
    void EmployeesDetailsView_ItemDeleted(Object sender, DetailsViewDeletedEventArgs e) {
        EmployeesGridView.DataBind();
    }
    void EmployeesGridView_OnSelectedIndexChanged(object sender, EventArgs e) {
        EmployeeDetailsObjectDataSource.SelectParameters["EmployeeID"].DefaultValue =
            EmployeesGridView.SelectedDataKey.Value.ToString();
        EmployeesDetailsView.DataBind();
    }
    void EmployeeDetailsObjectDataSource_OnInserted(object sender,
        ObjectDataSourceStatusEventArgs e) {
    
        EmployeeDetailsObjectDataSource.SelectParameters["EmployeeID"].DefaultValue =
            e.ReturnValue.ToString();
        EmployeesDetailsView.DataBind();
    }
    void EmployeeDetailsObjectDataSource_OnUpdated(object sender,
        ObjectDataSourceStatusEventArgs e) {
    
        if ((int)e.ReturnValue == 0)
            Msg.Text = "Employee was not updated. Please try again.";
    }
    void EmployeeDetailsObjectDataSource_OnDeleted(object sender,
        ObjectDataSourceStatusEventArgs e) {
    
        if ((int)e.ReturnValue == 0)
            Msg.Text = "Employee was not deleted. Please try again.";
    }
    void Page_Load() {
        Msg.Text = "";
    }
    </script>
    <html>
      <body>
        <form id="Form1" runat="server">
          <h3>ObjectDataSource Example</h3>
          <asp:Label id="Msg" runat="server" ForeColor="Red" />
          <asp:ObjectDataSource
              ID="EmployeesObjectDataSource"
              runat="server"
              TypeName="NorthwindData"
              SortParameterName="SortColumns"
              EnablePaging="true"
              SelectCountMethod="SelectCount"
              StartRowIndexParameterName="StartRecord"
              MaximumRowsParameterName="MaxRecords"
              SelectMethod="GetAllEmployees" >
          </asp:ObjectDataSource>
          <asp:ObjectDataSource
              ID="EmployeeDetailsObjectDataSource"
              runat="server"
              TypeName="NorthwindData"
              ConflictDetection="CompareAllValues"
              OldValuesParameterFormatString="{0}"
              SelectMethod="GetEmployee"
              InsertMethod="InsertEmployee"
              UpdateMethod="UpdateEmployee"
              DeleteMethod="DeleteEmployee"
              OnInserted="EmployeeDetailsObjectDataSource_OnInserted"
              OnUpdated="EmployeeDetailsObjectDataSource_OnUpdated"
              OnDeleted="EmployeeDetailsObjectDataSource_OnDeleted">
              <SelectParameters>
                  <asp:Parameter Name="EmployeeID" Type="Int32" />
              </SelectParameters>
          </asp:ObjectDataSource>
          <table cellspacing="10">
            <tr>
              <td valign="top">
                <asp:GridView ID="EmployeesGridView"
                    DataSourceID="EmployeesObjectDataSource"
                    AutoGenerateColumns="false"
                    AllowSorting="true"
                    AllowPaging="true"
                    PageSize="5"
                    DataKeyNames="EmployeeID"
                    OnSelectedIndexChanged="EmployeesGridView_OnSelectedIndexChanged"
                    RunAt="server">
                    <HeaderStyle backcolor="lightblue" forecolor="black"/>
                    <Columns>
                    <asp:ButtonField Text="Details..."
                    HeaderText="Show Details"
                    CommandName="Select"/>
    
                    <asp:BoundField DataField="EmployeeID" HeaderText="Employee ID"
                    SortExpression="EmployeeID" />
                    <asp:BoundField DataField="FirstName" HeaderText="First Name"
                    SortExpression="FirstName" />
                    <asp:BoundField DataField="LastName" HeaderText="Last Name"
                    SortExpression="LastName, FirstName" />
                    </Columns>
                </asp:GridView>
              </td>
              <td valign="top">
                <asp:DetailsView ID="EmployeesDetailsView"
                    DataSourceID="EmployeeDetailsObjectDataSource"
                    AutoGenerateRows="false"
                    EmptyDataText="No records."
                    DataKeyNames="EmployeeID"
                    Gridlines="Both"
                    AutoGenerateInsertButton="true"
                    AutoGenerateEditButton="true"
                    AutoGenerateDeleteButton="true"
                    OnItemInserted="EmployeesDetailsView_ItemInserted"
                    OnItemUpdated="EmployeesDetailsView_ItemUpdated"
                    OnItemDeleted="EmployeesDetailsView_ItemDeleted"
                    RunAt="server">
                    <HeaderStyle backcolor="Navy" forecolor="White"/>
                    <RowStyle backcolor="White"/>
                    <AlternatingRowStyle backcolor="LightGray"/>
                    <EditRowStyle backcolor="LightCyan"/>
                    <Fields>
                        <asp:BoundField DataField="EmployeeID" HeaderText="Employee ID"
                            InsertVisible="False" ReadOnly="true"/>
                        <asp:BoundField DataField="FirstName" HeaderText="First Name"/>
                        <asp:BoundField DataField="LastName" HeaderText="Last Name"/>
                        <asp:BoundField DataField="Address" HeaderText="Address"/>
                        <asp:BoundField DataField="City" HeaderText="City"/>
                        <asp:BoundField DataField="Region" HeaderText="Region"/>
                        <asp:BoundField DataField="PostalCode" HeaderText="Postal Code"/>
                    </Fields>
                  </asp:DetailsView>
                </td>
              </tr>
            </table>
          </form>
        </body>
      </html>
    
  8. Salve todos os arquivos e procure object.aspx.

  9. Interaja com a interface exibindo detalhes, editando funcionários, adicionando funcionários e excluindo funcionários.