Escolar Documentos
Profissional Documentos
Cultura Documentos
Conforme declarado anteriormente, DataTable é um conjunto de dados e consiste em linhas e colunas. A classe DataTable tem as
três coleções importantes a seguir:
•A coleção Columns é exposta como a propriedade Columns e é uma instância da classe DataColumnCollection.
Ele contém zero ou mais objetos DataColumn. Cada objeto DataColumn representa uma coluna ou campo
da DataTable, assim como uma coluna de banco de dados. Essas colunas definem a estrutura de uma
DataTable.
•Assim como uma tabela de banco de dados, uma DataTable também pode ter restrições, como restrições de chave
única e restrições de chave estrangeira. A coleção Constraints é exposta como a propriedade Constraints e é
uma instância da classe ConstraintCollection. Ele pode conter zero ou mais instâncias das classes
UniqueConstraint ou ForeignKeyConstraint.
Além das classes anteriores, há um objeto especial chamado DataView baseado em uma DataTable. Como o nome sugere,
DataView é usado para apresentar diferentes exibições de dados classificando e filtrando dados do DataTable. Observe que DataView não
tem existência independente e é sempre baseado em um DataTable.
Geralmente, você preencherá seu DataSet com os dados de uma fonte de dados, como um banco de dados do SQL Server.
No entanto, o DataSet está totalmente desconectado. A maioria dos objetos do DataSet explicados anteriormente podem ser criados
independentemente sem qualquer interação com qualquer fonte de dados. Isso significa que você pode criar programaticamente seu DataSet
sem se conectar a nenhuma fonte de dados. Por exemplo, você pode querer importar uma lista separada por vírgulas de dados de string
para uma tabela de banco de dados. Nesses casos, você pode criar objetos DataSet e DataTable programaticamente e preencher os dados.
Mais tarde, você pode salvar esses dados em uma tabela de banco de dados.
ponte entre a fonte de dados subjacente e o DataSet. DataAdapter entra em cena quando você deseja realizar qualquer um dos seguintes:
Antes de ver um exemplo de como preencher um DataSet e atualizar a fonte de dados, você precisa entender a arquitetura
do DataAdapter. Dê uma olhada na Figura 7-4.
184
Machine Translated by Google
Conforme mostrado na Figura 7-4, o DataAdapter usa quatro objetos Command para executar as consultas SELECT,
INSERT, UPDATE e DELETE. Cada comando é representado pelas propriedades SelectCommand, InsertCommand,
UpdateCommand e DeleteCommand do DataAdapter, respectivamente. Observe que esses objetos Command são os
mesmos que você viu no acesso a dados conectados. No entanto, cada um recebe uma tarefa específica de selecionar,
inserir, atualizar e excluir registros da fonte de dados. Assim como ocorre com os objetos Command padrão, a propriedade
CommandText desses objetos Command pode ser qualquer consulta SQL ou procedimento armazenado válido.
DataAdapter fornece o método Fill() que usa o objeto Command especificado pelo SelectCommand
propriedade e preenche o DataSet. Se você alterar o DataSet preenchido pelo método anterior e quiser propagar as
alterações de volta para a fonte de dados subjacente, será necessário definir outras propriedades (InsertCommand,
UpdateCommand e assim por diante) para instâncias de Command válidas. DataAdapter fornece outro método chamado
Update() que usa os objetos Command especificados pelas propriedades InsertCommand, UpdateCommand e DeleteCommand
e leva as alterações de um DataSet de volta para a fonte de dados subjacente.
185
Machine Translated by Google
O aplicativo é uma tela típica de entrada de dados. A caixa de combinação mostra uma lista de todos os IDs de
funcionários. Depois de selecionar um ID de funcionário, os detalhes desse funcionário (nome, sobrenome, telefone residencial
e notas) são exibidos nas caixas de texto. Os botões Inserir, Atualizar e Excluir executam as respectivas operações. Todas as
operações - INSERT, UPDATE e DELETE - são executadas no DataSet e não na tabela real do banco de dados. Depois que
todas as operações forem concluídas, você pode clicar no botão Salvar para fazer todas as alterações no banco de dados real.
O aplicativo usa a tabela Employees do banco de dados Northwind.
Agora vamos dissecar o aplicativo passo a passo e ver como o DataSet e o DataAdapter foram colocados
usar.
Preenchendo um DataSet
Se você vir o código-fonte do aplicativo anterior, encontrará algumas variáveis declaradas no nível do formulário. A declaração
é mostrada na Listagem 7-3.
A variável de string strConn armazena a string de conexão do banco de dados, que usa uma instalação local do SQL Server
Express conforme indicado pelo atributo da fonte de dados. Em seguida, as variáveis do tipo DataSet, SqlDataAdapter e
SqlConnection são declaradas. Você deve garantir que importou System.Data e System.
Data.SqlClient antes de declarar essas variáveis.
O manipulador de eventos Form_Load do formulário contém o código mostrado na Listagem 7-4.
186
Machine Translated by Google
O código cria um objeto SqlCommand e define sua propriedade CommandText para buscar todos os registros de
a tabela Empregados. A propriedade Connection é definida como o objeto SqlConnection criado anteriormente. O objeto SqlCommand
recém-criado é atribuído à propriedade SelectCommand da instância SqlDataAdapter.
A propriedade SelectCommand determina os registros a serem preenchidos no DataSet posteriormente.
Em seguida, o método Fill() do SqlDataAdapter é chamado. Leva dois parâmetros: o DataSet a ser preenchido e o nome do
DataTable resultante. Observe que o código não abre a conexão nem a fecha. Isso ocorre porque a classe SqlDataAdapter faz isso
internamente para nós. Por fim, um método auxiliar, FillEmployees(), é chamado e preenche a caixa de combinação com a lista de IDs de
funcionários. O método FillEmployees() será discutido posteriormente.
ÿ Observação A classe SqlDataAdapter fecha a conexão automaticamente para nós apenas se for
aberta pelo próprio SqlDataAdapter . Se a conexão for aberta antes de chamar o método Fill() , o SqlDataAdapter não
a fechará automaticamente.
O código primeiro armazena o ID do funcionário selecionado em uma variável de string. Para localizar o registro de funcionário
correspondente no DataSet, usamos o método Select() de DataTable, que aceita os critérios de seleção e retorna uma matriz de objetos DataRow
correspondentes a esses critérios. Em nosso exemplo, precisamos selecionar o funcionário cujo valor da coluna EmployeeID corresponda ao
selecionado na caixa de combinação. EmployeeID é a coluna de chave primária da tabela Employees e, portanto, sabemos que ela retornará apenas
um DataRow. O DataRow pode 187
Machine Translated by Google
ser acessado usando a notação típica de array. Observe como os nomes das colunas são usados para acessar os valores das colunas
individuais. Em vez de nomes de coluna, você poderia ter usado índices de coluna. Os vários valores de coluna são exibidos nas
respectivas caixas de texto.
Depois de inserir os detalhes de um novo funcionário a ser adicionado e clicar no botão Inserir, uma nova linha é adicionada à DataTable
subjacente. O código que torna isso possível é mostrado na Listagem 7-6.
Para atualizar uma linha existente, você deve localizá-la primeiro e depois atualizar os valores da coluna. Para encontrar uma linha
específica, você pode usar o mesmo método Select() que usamos anteriormente. Isso é mostrado na Listagem 7-7.
if (comboBox1.SelectedItem == nulo) {
188
Machine Translated by Google
O código seleciona o registro do funcionário que deve ser atualizado usando o método Select() da DataTable. O
método BeginEdit() da classe DataRow usa a linha no modo de edição. Os valores da coluna são então atribuídos. Finalmente,
o método EndEdit() da classe DataRow é chamado. Isso salva as alterações no DataTable subjacente.
excluir uma linha, primeiro você deve localizá-la e depois chamar o método Delete() nela. Isso é ilustrado na Listagem
7-8.
O código recupera a linha a ser excluída usando o método Select() da classe DataTable.
O método Delete() da classe DataRow marca a linha subjacente para exclusão. Por fim, a caixa de combinação é preenchida
novamente para que o ID do funcionário excluído não apareça.
seções anteriores, você inseriu, atualizou e excluiu objetos DataRow de uma DataTable. Sempre que você executa qualquer uma
dessas operações (inserir, atualizar ou excluir) em um DataRow, sua propriedade RowState é afetada automaticamente. A
propriedade RowState é uma enumeração do tipo DataRowState e indica o estado do DataRow. A Tabela 7-1 mostra vários
valores possíveis da enumeração DataRowState.
independente A linha foi criada, mas ainda não foi anexada à DataTable.
189
Machine Translated by Google
A propriedade RowState é usada pela função auxiliar FillEmployees(), conforme mostrado na Listagem 7-9.
O método FillEmployees() simplesmente itera através de cada DataRow da Employees DataTable e adiciona o EmployeeID
à caixa de combinação. Observe o código marcado em negrito. Antes de adicionar qualquer valor na caixa de combinação, o código
verifica se o RowState da linha foi excluído. Somente as linhas cujo RowState não é Deleted são adicionadas à caixa de combinação.
VALUES(@fname,@lname,@phone,@notes)";
cmdUpdate.CommandText =
"UPDATE empregados SET firstname=@fname,lastname=@lname,homephone=@phone
WHERE id do funcionário=@empid";
cmdDelete.CommandText = "EXCLUIR FROM funcionários ONDE funcionárioid=@empid";
190
Machine Translated by Google
pInsert[1].SourceColumn = "sobrenome";
pInsert[2] = new SqlParameter("@phone", SqlDbType.VarChar);
pInsert[2].SourceColumn = "telefone residencial"; pInsert[3] = new
SqlParameter("@notas", SqlDbType.VarChar); pInsert[3].SourceColumn =
"notas"; foreach (SqlParameter p em pInsert) {
cmdInsert.Parameters.Add(p); }
cmdUpdate.Parameters.Add(p); }
cmdDelete.Parameters.Add(p); }
da.InsertCommand = cmdInsert;
da.UpdateCommand = cmdUpdate;
da.DeleteCommand = cmdDelete;
da.Update(ds,"Empregados");
ds.AcceptChanges(); }
O código cria três objetos SqlCommand para operações INSERT, UPDATE e DELETE, respectivamente. A
propriedade Connection desses objetos SqlCommand é definida como o mesmo objeto SqlConnection que declaramos
inicialmente na parte superior. A propriedade CommandText de cada SqlCommand é definida para a instrução SQL correspondente.
Observe o uso do caractere @ para representar parâmetros. Para cada um desses espaços reservados de
parâmetro, um objeto SqlParameter precisa ser criado. Isso é feito declarando três arrays dos objetos SqlParameter:
pInsert, pUpdate e pDelete.
Em seguida, cada elemento da matriz é instanciado como um objeto SqlParameter passando o nome do
parâmetro e o tipo de dados do parâmetro no construtor da classe SqlParameter. A propriedade SourceColumn de
SqlParameter especifica o nome do DataColumn que fornecerá o valor para o parâmetro. Todos os parâmetros das
matrizes correspondentes são adicionados à coleção Parameters do respectivo objeto SqlCommand. Esses três
objetos SqlCommand são atribuídos às propriedades InsertCommand, UpdateCommand e DeleteCommand da instância
SqlDataAdapter que declaramos na parte superior.
191
Machine Translated by Google
O método Update() da classe SqlDataAdapter é chamado e leva todas as alterações — inserções, atualizações e
exclusões — do DataSet de volta ao banco de dados. O método Update() recebe dois parâmetros: o DataSet a ser atualizado
e o nome do DataTable do DataSet a ser atualizado. Depois que as alterações são salvas no banco de dados subjacente, as
propriedades RowState de todos os objetos DataRow devem se tornar inalteradas. Isso é feito chamando o método
AcceptChanges() da classe DataSet.
É isso! Agora você pode executar o aplicativo e testá-lo quanto à funcionalidade esperada.
dos recursos poderosos da classe DataSet é que você pode serializá-lo no formato XML, o que significa que os dados
relacionais podem ser salvos no formato XML. Esse recurso é útil ao trabalhar no modo offline e ao transportar dados para
sistemas heterogêneos.
O método WriteXml() da classe DataSet grava o conteúdo do DataSet em um fluxo ou físico
arquivo em formato XML. Opcionalmente, você também pode adicionar informações de esquema. Para ilustrar o uso de
WriteXml(), você precisa criar um aplicativo como o mostrado na Figura 7-6.
Figura 7-6. Aplicativo que grava o conteúdo do DataSet como um arquivo XML
O aplicativo consiste em uma caixa de texto para especificar o caminho do arquivo XML de saída. Os dois primeiros
botões de opção especificam se as informações do esquema devem ser incluídas. O último botão de opção especifica se
você deseja gravar o original, bem como os valores atuais no arquivo. Se você selecionar este botão de opção, os DiffGrams
dos valores originais e atuais serão gravados no arquivo.
ÿ Observação DiffGram é um formato XML especial que armazena valores de linha originais e atuais. O SQL Server 2000
introduziu recursos para atualizar o banco de dados via UpdateGrams. DiffGram é um subconjunto de UpdateGram.
Se selecionada, a caixa de seleção abre o arquivo XML salvo em um navegador. O botão Salvar realmente grava o
DataSet no arquivo especificado. O manipulador de eventos Click do botão Salvar contém o código mostrado na Listagem 7-11.
192
Machine Translated by Google
Integrated security=true");
da.Fill(ds, "employees"); if (radioButton1.Checked)
{ ds.WriteXml(textBox1.Text, XmlWriteMode.IgnoreSchema); }
if (radioButton2.Checked) { ds.WriteXml( textBox1.Text,
XmlWriteMode.WriteSchema); } if (radioButton3.Checked) { foreach
(linha DataRow em ds.Tables[0].Rows) { row.SetModified(); }
ds.WriteXml(textBox1.Text, XmlWriteMode.DiffGram ); } if
(checkBox1.Checked) { Process.Start(textBox1.Text); } }
O código cria um novo DataSet e um SqlDataAdapter. Uma das sobrecargas dos construtores SqlDataAdapter
aceita a consulta SELECT e a string de conexão do banco de dados, e é essa sobrecarga que usamos.
O DataSet é então preenchido usando o método Fill() do DataAdapter. O nome da DataTable é especificado
como Employees.
Em seguida, uma série de condições if verifica o status dos botões de opção. Em cada uma das condições
if, o método WriteXml() da classe DataSet é chamado, que grava o conteúdo do DataSet no fluxo ou arquivo de
disco especificado. Observe que, embora cada uma das condições if chame WriteXml(), o segundo parâmetro —
XmlWriteMode — é diferente em cada caso.
A enumeração XmlWriteMode controla duas coisas. Primeiro, ele especifica se as informações do esquema
devem ser gravadas junto com o conteúdo XML. Em segundo lugar, ele decide se os dados XML de saída conterão
apenas os valores atuais ou ambos os valores originais e atuais. Como você viu no exemplo anterior, o último
formato é chamado DiffGram. Os três valores possíveis da enumeração XmlWriteMode são mostrados na Tabela 7-2.
193
Machine Translated by Google
Valor Descrição
Ignorar esquema Grava o conteúdo do DataSet como dados XML. Nenhuma informação do XSD Schema é
escrito.
WriteSchema Grava o conteúdo do DataSet como dados XML. Além disso, grava as informações do
XSD Schema junto com os dados.
DiffGram Grava o conteúdo do DataSet como marcação XML DiffGram. O DiffGram armazena os valores de
coluna atuais e originais.
Observe a condição if para radioButton3. Como não fizemos nenhuma alteração no DataSet como tal, o código marca
deliberadamente cada linha como modificada. Isso é feito usando o método SetModified() da classe DataRow. Dessa forma,
poderemos ver como o formato DiffGram armazena valores antigos e novos.
Finalmente, o arquivo XML salvo é aberto em um navegador usando o método Start() da classe Process.
A Figura 7-7 mostra um exemplo de execução do aplicativo sem salvar nenhuma informação de esquema. Da mesma
forma, as Figuras 7-8 e 7-9 mostram o arquivo XML de saída com informações de esquema e DiffGram, respectivamente.
194
Machine Translated by Google
195
Machine Translated by Google
Observe como as informações do esquema são emitidas na Figura 7-8. Além disso, examine cuidadosamente a
Figura 7-9 . Essa saída XML está no formato DiffGram. Veja como a seção <diffgr:before> armazena os valores originais
dos DataRows, enquanto os valores atuais são exibidos na parte superior.
WriteXml() grava dados e, opcionalmente, informações do esquema XSD. E se você precisar extrair apenas as informações
do esquema e não os dados em si? O método WriteXmlSchema() faz esse trabalho escrevendo apenas o esquema do
DataSet e não os dados. Para ilustrar o uso de WriteXmlSchema(), você pode modificar o aplicativo anterior para incluir um
botão de opção adicional. A nova interface do aplicativo é mostrada na Figura 7-10.
Se você selecionar o botão de opção recém-adicionado intitulado Salvar somente esquema, apenas o esquema do
DataSet será salvo. A Listagem 7-12 mostra a versão modificada do código.
da.Fill(ds, "funcionários"); if
(radioButton1.Checked)
{ ds.WriteXml(textBox1.Text,
XmlWriteMode.IgnoreSchema); } if (radioButton2.Checked)
{ ds.WriteXml(textBox1.Text, XmlWriteMode.WriteSchema); }
196
Machine Translated by Google
if (radioButton3.Checked) {
linha.SetModified(); }
ds.WriteXml(textBox1.Text,
XmlWriteMode.DiffGram); } if (radioButton4.Checked)
{ ds.WriteXmlSchema(textBox1.Text); } if (checkBox1.Checked) {
Process.Start(textBox1.Text); }
Observe o código marcado em negrito. O código chama o método WriteXmlSchema() passando o arquivo no qual as
informações do esquema serão armazenadas. Você observará que o esquema obtido por este método é o mesmo obtido
pelo método WriteXml() com XmlWriteMode.WriteSchema. No entanto, nenhum dado é gravado no arquivo.
• Da mesma forma, o método GetXmlSchema() retorna o esquema XML do DataSet como uma
string. Como esses métodos retornam cadeias de caracteres, eles incorrem em mais sobrecarga
do que WriteXml() e WriteXmlSchema().
você aprendeu a serializar o conteúdo do DataSet como dados XML. Pode haver casos em que você gostaria de fazer o
oposto, ou seja, pode precisar ler dados XML em um DataSet e processá-los posteriormente. O método ReadXml() da classe
DataSet é a contraparte do método WriteXml() que já discutimos e permite que você leia dados XML em um DataSet.
ÿ Nota Nos exemplos a seguir, você frequentemente precisará de arquivos XML contendo um esquema e dados.
É recomendável executar o exemplo anterior (Figura 7-10) e salvar os arquivos XML resultantes em seu disco para
uso posterior.
Para ilustrar o uso de ReadXml(), você precisa desenvolver um aplicativo como o mostrado na Figura 7-11.
197
Machine Translated by Google
O aplicativo consiste em uma caixa de texto para aceitar o caminho do arquivo XML de origem. Há uma série
de botões de opção que determinam como o documento XML será lido pelo DataSet. O botão Ler aciona a
operação de leitura. A Listagem 7-13 mostra o código completo que lê os dados XML em um DataSet.
modo = XmlReadMode.Auto;
} if (radioButton2.Checked) {
modo = XmlReadMode.DiffGram;
} if (radioButton3.Checked) {
modo = XmlReadMode.Fragment; }
if (radioButton4.Checked) {
modo = XmlReadMode.IgnoreSchema; }
if (radioButton5.Checked) {
modo = XmlReadMode.InferSchema;
} if (radioButton6.Checked) {
modo = XmlReadMode.ReadSchema;
}
198
Machine Translated by Google
ds.ReadXml(textBox1.Text, modo);
MessageBox.Show("Arquivo XML lido com sucesso!"); }
O código cria um novo objeto DataSet. Em seguida, ele declara uma variável do tipo de enumeração XmlReadMode.
Essa enumeração desempenha um papel importante na decisão de como os dados XML serão carregados no DataSet.
Você pode ver todos os valores possíveis da enumeração XmlReadMode na Tabela 7-3. Em seguida, uma série de
condições if verifica o status dos vários botões de opção e define o valor da variável XmlReadMode.
Finalmente, o método ReadXml() da classe DataSet é chamado.
Valor Descrição
Auto Usa o modo de leitura mais apropriado dos valores restantes (o padrão)
DiffGram Carrega um DiffGram e aplica as alterações
Fragmento Carrega fragmentos XML como os criados ao usar a cláusula FOR XML
O método ReadXml() tem várias sobrecargas. A que usamos aceita o nome do arquivo XML a ser lido e o valor
XmlReadMode. Após a conclusão de ReadXml(), o DataSet preencheu os objetos DataTable, dependendo do documento XML de
origem. Por exemplo, se você usar o arquivo EmployeesTable.xml que criamos anteriormente, seu DataSet conterá uma DataTable
chamada Employees.
As opções de XmlReadMode precisam de mais explicações porque há várias possibilidades durante o
operação de leitura. Essas opções são discutidas a seguir.
enumeração XmlReadMode usa o mecanismo mais apropriado ao carregar os dados XML. Se os dados forem um DiffGram,
ele definirá XmlReadMode como DiffGram. Se o DataSet já tiver um esquema ou o documento XML contiver um esquema
embutido, ele definirá XmlReadMode como ReadSchema. Por fim, se o DataSet ainda não tiver um esquema e o documento
XML não contiver um esquema embutido, ele definirá XmlReadMode como InferSchema.
DiffGram da enumeração XmlReadMode é usada exclusivamente com DiffGrams. Geralmente, esses DiffGrams serão gerados
usando o método WriteXml() do DataSet. O esquema do DataSet e do DiffGram deve corresponder para que os dados sejam
lidos com êxito. Como o DiffGram armazena os valores originais e atuais de DataRows, as alterações são aplicadas depois que
o DiffGram é carregado no DataSet.
199
Machine Translated by Google
O documento XML que você deseja carregar em um DataSet pode conter informações de esquema incorporadas a ele.
Se você quiser ignorar esse esquema, deverá usar a opção IgnoreSchema da enumeração XmlReadMode.
Se o DataSet já tiver um esquema e os dados XML que estão sendo carregados não corresponderem a esse esquema, os dados serão
descartados.
InferSchema de XmlReadMode ignora as informações de esquema dos dados XML de origem, se presentes, e
carrega os dados em um DataSet. Se o DataSet já tiver seu esquema, ele será estendido para acomodar os
novos dados. No entanto, se houver alguma incompatibilidade entre o esquema existente e o esquema recém-
inferido, uma exceção será gerada.
O formulário consiste em um único controle MenuStrip. Os itens de menu são armazenados em um arquivo XML, conforme mostrado na
Listagem 7-14. Salve este arquivo como menus.xml na pasta Bin\Debug do seu aplicativo.
200
Machine Translated by Google
<submenu>Cortar</submenu>
<submenu>Copiar</submenu>
<submenu>Colar</submenu> </
topmenu> <topmenu text="Ajuda">
<submenu>Ajuda</submenu>
<submenu>Pesquisar</submenu>
<submenu>Sobre</submenu> </
topmenu> </menus>
O elemento raiz do arquivo XML é <menus>. Dentro pode haver zero ou mais itens <topmenu>, que
representam os itens de menu de nível superior. O atributo de texto de <topmenu> indica o texto desse menu. O elemento
<topmenu> pode conter zero ou mais elementos <submenu>, que indicam submenus dos menus de nível superior. O texto dos
submenus é especificado no valor do elemento <submenu>.
Vamos ver como esse arquivo pode ser carregado em um DataSet e como os dados podem ser acessados. A Listagem 7-15
mostra o manipulador de eventos Load do formulário com o código necessário.
}
}
201
Machine Translated by Google
O código cria um novo DataSet e lê o arquivo menus.xml que criamos anteriormente. Ao ler isso
arquivo, o DataSet faz algumas coisas interessantes:
1. Ele observa o aninhamento dos dados XML no arquivo e cria dois objetos DataTable.
A primeira DataTable armazena todos os menus principais e a segunda DataTable
armazena todos os submenus.
2. Ele cria objetos DataRow no menu superior DataTable e adiciona uma DataColumn a eles.
O valor contido nessas colunas é o valor do atributo text do elemento <topmenu>.
3. Ele faz algo semelhante para os submenus DataTable, mas carrega os valores dos elementos
dos itens <submenu> na coluna.
4. Ele define um DataRelation entre as duas tabelas adicionando automaticamente uma coluna
inteira a ambos os objetos DataTable.
O código itera por todas as linhas do primeiro DataTable (o DataTable que armazena os menus superiores) e
adiciona objetos ToolStripMenuItem ao MenuStrip. O método GetChildRows() é chamado em cada DataRow do menu
superior DataTable. Este método aceita um objeto DataRelation e retorna todos os objetos DataRow da tabela filho
correspondente a esse relacionamento. No nosso caso, o submenu DataTable é o filho DataTable. O valor de retorno de
GetChildRows() é uma matriz de objetos DataRow. O segundo loop foreach percorre todos os elementos dessa matriz e
adiciona subitens à coleção DropDownItems da classe ToolStripMenuItem.
permite que você leia dados e, opcionalmente, informações do esquema. No entanto, às vezes pode ser necessário
extrair apenas as informações do esquema do arquivo XML e não os dados. A classe DataSet fornece dois métodos
que permitem extrair informações de esquema do XML de origem. Eles são ReadXmlSchema() e InferXmlSchema().
ReadXmlSchema() aceita o XML com um esquema embutido e lê apenas a parte do esquema dele. O esquema
é então carregado no DataSet. E se o seu documento XML não contiver um esquema embutido?
É aí que o método InferXmlSchema() entra em cena. O método InferXmlSchema() observa a marcação XML
fornecida e, em seguida, cria um esquema correspondente automaticamente. O esquema é então carregado no DataSet.
Para ilustrar esses dois métodos, você precisa desenvolver um aplicativo como o mostrado na Figura 7-13.
202
Machine Translated by Google
O aplicativo consiste em uma caixa de texto para especificar o arquivo XML de origem. Os dois botões de opção
permitem que você decida se ReadXmlSchema() ou InferXmlSchema() deve ser chamado. O código para o botão Ler
lê o esquema em um DataSet e o exibe em uma caixa de mensagem. O código que lê o esquema é mostrado na
Listagem 7-16.
MessageBox.Show(ds.GetXmlSchema()); }
O código cria um novo objeto DataSet. Dependendo do botão de opção selecionado, o código chama
ReadXmlSchema() ou InferXmlSchema(). ReadXmlSchema() aceita o documento XML de origem como um
parâmetro e carrega o esquema embutido do documento no DataSet. Nenhum dado é carregado.
O método InferXmlSchema() aceita o documento XML de origem e um array de namespaces (nulo em nosso
exemplo) e infere o esquema a partir dos dados. Novamente, nenhum dado é carregado. O esquema carregado é
mostrado em uma caixa de mensagem chamando o método GetXmlSchema() do DataSet. A Figura 7-14 mostra o
esquema carregado usando ReadXmlSchema(), enquanto a Figura 7-15 mostra o esquema carregado usando InferXmlSchema().
203
Machine Translated by Google
Como você pode ver, o esquema carregado por ambos os métodos é idêntico em nosso exemplo.
204
Machine Translated by Google
Ambas as listagens representam o código que insere um novo DataRow em um DataTable. Compare as listas
cuidadosamente. Na Listagem 7-17, acessamos a DataTable Employees e suas colunas especificando-as entre aspas duplas.
Isso significa que você precisa se lembrar desses nomes quando estiver codificando. No entanto, a Listagem 7-18 parece
diferente. Você notará que ele usa a propriedade Employees para criar uma nova linha. Além disso, ele usa nomes de coluna
como FirstName e LastName como se fossem propriedades. Obviamente, a segunda versão é muito mais fácil de codificar e é
muito mais organizada, o que demonstra o que são DataSets digitados.
Um DataSet tipado é uma classe que deriva internamente de DataSet como uma classe base. Ele estende esta classe base
ainda mais e adiciona certas propriedades e métodos que facilitam a vida do desenvolvedor. Ao usar um DataSet tipado, você
pode acessar objetos DataTable e DataColumn usando seus nomes fortemente tipados em vez da sintaxe de coleção. Um
DataSet digitado tem um esquema XSD anexado a ele que define os objetos DataTable e DataColumn do DataSet.
Usando o Visual Studio para criar um DataSet tipado Agora que você sabe o que é um
DataSet tipado, vamos criar um para nossa tabela Employees. Para fazer isso, primeiro você precisa adicionar um DataSet
digitado ao seu projeto. A Figura 7-16 mostra a caixa de diálogo Add New Item do Visual Studio, por meio da qual você pode
adicionar um novo DataSet digitado.
205
Machine Translated by Google
Depois que estiver no designer do DataSet, você poderá ver a caixa de ferramentas do DataSet, mostrada na Figura 7-17.
Como você pode ver, a caixa de ferramentas tem itens como DataTable e Relation que você pode arrastar e soltar
o designer DataSet. Para nosso exemplo, você precisa arrastar e soltar uma DataTable no designer DataSet e definir sua propriedade
Name como Employees. Para adicionar colunas ao DataTable, você pode clicar com o botão direito do mouse e adicionar o número
necessário de colunas (consulte a Figura 7-18).
O nome e o tipo de dados de cada coluna podem ser definidos por meio da janela de propriedades. Por exemplo, a Figura
7-19 mostra a janela Propriedades da coluna EmployeeID.
206
Machine Translated by Google
Depois de projetar a DataTable Employees, ela deve se parecer com a Figura 7-20.
À medida que você projeta o DataSet no designer, o Visual Studio cria uma classe que herda da classe DataSet
e adiciona determinadas propriedades e métodos a ela. Ele também cria certos arquivos de suporte, como você pode ver
no Solution Explorer (consulte a Figura 7-21).
207
Machine Translated by Google
Em seguida, você precisa projetar o formulário principal de seu aplicativo, conforme mostrado na Figura 7-22.
O aplicativo se comporta exatamente da mesma forma que o mostrado na Figura 7-6 anteriormente, mas desta vez ele usa nosso
DataSet digitado. A Listagem 7-19 mostra as declarações de variáveis no nível do formulário.
Observe a linha marcada em negrito. O código declara uma variável do nosso DataSet digitado, que tem o mesmo nome do arquivo
DataSet XSD Schema. Este DataSet digitado é preenchido no evento Load do formulário. O código no evento Load permanece o mesmo
de antes, mas para completar, ele é mostrado na Listagem 7-20.
208
Machine Translated by Google
O código usa um SqlDataAdapter e chama seu método Fill() para preencher o DataSet digitado. Uma Coisa
a observar aqui é que o nome do DataTable especificado no método Fill() deve corresponder ao nome do DataTable
que você criou no DataSet digitado. A Listagem 7-21 mostra a versão modificada do código responsável por inserir,
atualizar e deletar objetos DataRow.
(EmployeesDataSet.EmployeesRow[])ds.Employees.Select($"EmployeeID={id}");
linhas[0].Delete();
PreencheFuncionários();
}
209
Machine Translated by Google
Observe as alterações feitas no código original. No manipulador de eventos Click do botão Inserir, o novo
DataRow é criado chamando NewEmployeesRow(). O DataSet digitado mostra automaticamente os objetos
DataTable disponíveis como propriedades e cada DataTable fornece o método NewEmployeesRow() para criar uma
nova linha. A linha recém-criada é do tipo EmployeesRow, que é uma classe gerada pelo Visual Studio na classe
EmployeesDataSet. EmployeesRow expõe cada coluna da linha como uma propriedade, e essas propriedades podem
receber novos valores. A linha recém-criada é então adicionada à DataTable Employees usando seu método
AddEmployeesRow(). Há modificações semelhantes nos manipuladores de eventos Click dos botões Atualizar e Excluir.
Usando a ferramenta xsd.exe para criar um DataSet digitado Embora o Visual Studio
forneça uma maneira visual de criar DataSets digitados, o .NET Framework também fornece uma ferramenta de linha
de comando chamada xsd.exe que pode gerar DataSets tipados para você. A ferramenta aceita um esquema XML
para um DataSet e gera a classe DataSet digitada. Embora não discutamos a ferramenta xsd.exe em detalhes, aqui está
um exemplo de uso dela:
O comando aceita o nome de um arquivo de esquema XML (EmployeesDataSet.xsd neste caso). A opção /dataset
indica que a ferramenta deve gerar um DataSet digitado com base nesse esquema XML. A opção /idioma especifica o
idioma a ser usado para a classe DataSet digitada. Em nosso exemplo, especificamos a linguagem como C# (CS).
Por fim, a opção /namespace especifica o namespace no qual a classe DataSet digitada será colocada.
A saída do comando anterior será um arquivo de classe chamado EmployeesDataSet.cs. Você pode compilar
este arquivo de classe separadamente em um assembly ou adicioná-lo ao seu projeto existente junto com outras classes.
Resumo
O ADO.NET é uma parte muito importante do .NET Framework geral. Aplicativos orientados a dados modernos tendem
a funcionar com armazenamentos de dados relacionais e hierárquicos. O modelo de objeto ADO.NET, embora
principalmente inclinado para RDBMSs, tem forte integração com XML.
Este capítulo forneceu uma visão completa dos recursos XML do ADO.NET. Você aprendeu a trabalhar com
Dados XML no modo conectado e desconectado. A classe DataSet é a base do modelo desconectado do ADO.NET
e permite que você leia e grave dados XML e trabalhe com esquemas. Além disso, DataSets digitados facilitam seu
desenvolvimento, fornecendo nomes DataTable e DataColumn digitados.
210
Machine Translated by Google
CAPÍTULO 8
Serialização XML
Seus aplicativos .NET consistem em uma ou mais classes. Os objetos dessas classes são usados para armazenar informações de
estado. Enquanto seus objetos estiverem disponíveis na memória de seu aplicativo, essas informações de estado estarão prontamente
disponíveis. Mas e se você quiser manter o estado do objeto durante os desligamentos do aplicativo? A princípio, você pode pensar
em salvar o estado do objeto em um banco de dados relacional. No entanto, os bancos de dados geralmente armazenam informações
em formato relacional, enquanto os objetos costumam ter uma estrutura hierárquica. Além disso, você precisaria criar muitas tabelas
no banco de dados por conta própria. Armazenar dados de objeto em um banco de dados vem com suas próprias despesas gerais.
Não seria bom se todo o estado do objeto pudesse ser armazenado em uma mídia e recuperado posteriormente? Isso é o que a
serialização oferece.
A serialização é um processo pelo qual o estado do objeto é persistido em um meio. O meio pode ser um arquivo de disco físico,
memória ou até mesmo um fluxo de rede. Os objetos serializados podem ser recuperados posteriormente em seu aplicativo por um
processo chamado desserialização. O .NET Framework fornece amplo suporte para serialização e usa serialização em muitos lugares.
Serviços Web, serviços WCF e API Web são alguns locais onde a serialização é muito usada. Neste capítulo, você aprenderá sobre os
seguintes tópicos:
• XML: Objetos serializados dessa forma são armazenados como XML simples. Se você estiver falando com
vários sistemas heterogêneos, esse formato será útil. Por exemplo, seus aplicativos .NET podem
serializar objetos como documentos XML, e um aplicativo Java pode ler esses objetos serializados
usando seu analisador XML padrão e trabalhar mais com os dados.
• Simple Object Access Protocol (SOAP): Objetos serializados desta forma armazenam
informações de acordo com os padrões SOAP. SOAP é o pilar central dos serviços da web.
O protocolo SOAP é baseado em XML.
A serialização usando o formato binário está além do escopo deste livro. Este capítulo discute a serialização usando
os formatos XML e SOAP.
ÿ Observação Em aplicativos da Web modernos, o JSON está se tornando cada vez mais popular como formato para
transferência de dados. Os aplicativos que usam JSON precisam de um mecanismo para serializar e desserializar objetos no formato
JSON. Embora o WCF e a API da Web possam lidar com dados JSON, não discutiremos isso neste livro.
A outra maneira de classificar a serialização é baseada na profundidade da serialização. Os dois tipos com base na
profundidade da serialização são os seguintes:
No .NET Framework, a profundidade da serialização depende da classe do serializador que você usa para
serialização e desserialização de objetos.
•A classe DataContractSerializer serializa objetos no formato XML. Ele foi adicionado como
parte do Windows Communication Foundation (WCF) e reside no namespace
System.Runtime.Serialization. A serialização feita usando DataContractSerializer é
profunda por natureza.
212
Machine Translated by Google
O aplicativo consiste em uma classe chamada Employee com cinco propriedades públicas: EmployeeID, FirstName,
Sobrenome, Telefone residencial e Notas. Existem cinco caixas de texto que aceitam valores para essas
propriedades. Os dois botões, Serialize e Deserialize, fazem o trabalho de serializar e desserializar o objeto
Employee, respectivamente. A caixa de seleção determina se o documento XML serializado será aberto em um
navegador para exibição.
Antes de poder usar a classe XmlSerializer, você deve criar a classe Employee mostrada na Listagem 8-1.
A classe consiste em cinco propriedades públicas para armazenar várias informações sobre um funcionário,
ou seja, EmployeeID, FirstName, LastName, HomePhone e Notes.
O manipulador de eventos Click do botão Serialize contém o código mostrado na Listagem 8-2.
213
Machine Translated by Google
O código cria uma instância da classe Employee. Em seguida, ele atribui valores de várias caixas de texto às
propriedades correspondentes da classe Employee. Um FileStream é então criado para gravação em um arquivo de
disco físico (Employee.xml). Esse fluxo é usado durante a serialização do objeto. Em seguida, o código cria um objeto da
classe XmlSerializer. Conforme mencionado anteriormente, a classe XmlSerializer permite serializar dados no formato
XML.
Existem várias sobrecargas do construtor XmlSerializer e o código usa aquele que aceita
o tipo de classe cujos objetos devem ser serializados. As informações de tipo sobre a classe Employee são
obtidas usando a palavra-chave typeof. O método Serialize() de XmlSerializer serializa um objeto para um fluxo
especificado, TextWriter ou XmlWriter.
Como nosso exemplo usa um FileStream para serializar o objeto Employee, após a conclusão da serialização,
o fluxo está fechado. Por fim, os dados serializados do arquivo XML são exibidos em um navegador usando o
método Start() da classe Process.
O manipulador de eventos Click do botão Deserialize contém o código mostrado na Listagem 8-3.
O código cria um FileStream apontando para o mesmo arquivo que foi criado durante o processo de
serialização. Observe que desta vez o arquivo é aberto no modo Abrir e não no modo Criar. Em seguida, um objeto
de XmlSerializer é criado como antes.
O método Deserialize() da classe XmlSerializer aceita um Stream, um TextReader ou um XmlReader do
qual o objeto deve ser lido para desserialização. Em seguida, ele retorna o objeto desserializado. Os dados desserializados
são sempre retornados como um objeto e precisam ser convertidos para o tipo Funcionário. Em seguida, vários valores
de propriedade do objeto desserializado são atribuídos às respectivas caixas de texto.
214
Machine Translated by Google
Para testar o aplicativo, execute-o, insira alguns valores nas caixas de texto e clique no botão Serializar.
A Figura 8-2 mostra um exemplo de documento XML obtido pela execução do aplicativo anterior.
Examine cuidadosamente a marcação XML resultante. O nome da classe (Funcionário) tornou-se o nome do
elemento raiz. Os elementos como <EmployeeID>, <FirstName> e <LastName> têm o mesmo nome que as
propriedades correspondentes da classe Employee. Agora feche o aplicativo e execute-o novamente. Desta vez, clique
no botão Deserialize. Você descobrirá que as caixas de texto mostram os valores de propriedade que você especificou
durante a última execução do aplicativo.
215
Machine Translated by Google
Objeto não referenciado Esse evento é gerado quando os dados que estão sendo desserializados contêm algum tipo
reconhecido que não é usado ou não é referenciado. O evento recebe um argumento de
evento do tipo UnreferencedObjectEventArgs que fornece mais informações sobre o evento.
Este evento aplica-se apenas ao XML codificado por SOAP.
Para ilustrar o uso desses eventos, você precisa modificar o aplicativo anterior para aquele mostrado na Listagem 8-4.
216
Machine Translated by Google
Observe o código marcado em negrito. Depois de declarar a instância da classe XmlSerializer, ele conecta
três manipuladores de eventos — UnknownAttribute, UnknownElement e UnknownNode — que simplesmente exibem
uma caixa de mensagem mostrando o nome do atributo, elemento ou nó e o número da linha na qual o atributo,
elemento , ou o nó é encontrado. Observe como o parâmetro do argumento do evento é usado para extrair informações
sobre o conteúdo inesperado.
Para testar esses eventos, modifique o arquivo XML serializado manualmente, conforme mostrado na Listagem 8-5.
<?xml versão="1.0"?>
<Employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://
www.w3.org/2001/XMLSchema" EmpCode="E001"> <EmployeeID >1</EmployeeID>
<FirstName>Nancy</FirstName> <LastName>Davolio</LastName>
<HomePhone>(206) 555-9857</HomePhone>
<Notas>A educação inclui bacharelado em psicologia pela Colorado State University em 1970.
Ela também completou "The Art of the Cold Call". Nancy é membro do Toastmasters International.</Notes>
<OfficePhone>(206) 555-1234</OfficePhone>
</Empregado>
tipos simples; os membros da classe Employee eram tipos simples, como um inteiro e uma string. No entanto, as classes
do mundo real costumam ser complexas. Eles podem conter membros que são tipos de classe, tipos enumerados ou até
matrizes. A classe XmlSerializer fornece suporte para esses tipos complexos, e é isso que você verá no próximo exemplo.
A interface do usuário do aplicativo agora muda para se parecer com a mostrada na Figura 8-4.
217
Machine Translated by Google
As primeiras cinco caixas de texto permanecem as mesmas do exemplo anterior. No entanto, seis caixas de texto e uma
caixa de combinação são novas. As caixas de texto recém-adicionadas capturam as informações de e-mail, rua, cidade,
estado, país e código postal do funcionário. A caixa de combinação captura o tipo de funcionário (permanente ou contratado).
Para armazenar as informações de endereço dos funcionários, você precisa adicionar uma propriedade chamada
Address à classe Employee. A própria propriedade Address é do tipo Address. A classe Address é mostrada na Listagem 8-6.
Essa classe tem cinco propriedades públicas para armazenar endereço, cidade, estado, país e código postal,
respectivamente.
Para armazenar o tipo de funcionário, você precisa adicionar uma propriedade chamada Type à classe Employee. A
propriedade Type será uma enumeração do tipo EmployeeType, que contém dois valores: Permanent e Contract.
A enumeração EmployeeType é mostrada na Listagem 8-7.
Contrato permanente }
As informações de e-mail são armazenadas em uma propriedade chamada Emails. Um funcionário pode ter mais de
um endereço de e-mail e, portanto, essa propriedade é do tipo string array. A Listagem 8-8 mostra a versão modificada da
classe Employee.
218
Machine Translated by Google
Listagem 8-8. A classe Employee após adicionar propriedades de endereço, tipo e e-mails
O código é essencialmente o mesmo dos exemplos anteriores. No entanto, ele define as propriedades recém-
adicionadas aos valores correspondentes das caixas de texto e da caixa de combinação. Observe como a propriedade
complexa Address é definida. Além disso, observe como os e-mails separados por vírgulas inseridos na caixa de texto do e-
mail são convertidos em uma matriz de strings usando o método Split(). Depois que o objeto Employee é serializado chamando
Serialize(), o documento XML serializado se parece com o mostrado na Figura 8-5.
219
Machine Translated by Google
Examine cuidadosamente os dados XML serializados. O endereço é representado pelo nó <Address>, cujo
nome é derivado da propriedade Address da classe Employee. O nó <Address> tem cinco nós filhos: <Street>,
<City>, <State>, <Country> e <PostalCode>. Seus nomes são derivados das respectivas propriedades da classe
Address.
O elemento <Type> representa a propriedade Type da classe Employee. O valor de enumeração,
Permanent, é armazenado na marcação XML. Por fim, o nó <Emails> representa a propriedade Emails e seus nós
filhos nada mais são do que elementos individuais da matriz. Como os e-mails são armazenados em uma matriz de
string, os valores individuais são incluídos em elementos <string></string>.
A Listagem 8-10 mostra o código no evento Click do botão Deserialize.
220
Machine Translated by Google
textBox2.Text = emp.FirstName;
textBox3.Text = emp.LastName;
textBox4.Text = emp.HomePhone;
textBox5.Text = emp.Notas;
comboBox1.SelectedIndex = (emp.Type == EmployeeType.Permanent?0:1);
textBox6.Text=emp.Address.Street; textBox7.Text=emp.Address.City;
textBox8.Text=emp.Address.State; textBox9.Text=emp.Address.Country;
textBox10.Text=emp.Address.PostalCode; textBox11.Text = string.Join(",", emp.Emails);
stream.Close(); }
O código é praticamente o mesmo dos exemplos anteriores. Ele desserializa o objeto Employee serializado
anteriormente usando a classe XmlSerializer. Os valores de propriedade são atribuídos a vários controles no formulário.
Observe como a propriedade Emails é convertida em uma string separada por vírgulas usando o método Join() da classe
string. Vale a pena observar os seguintes pontos ao serializar tipos complexos:
•Para serializar e desserializar valores enumerados, o aplicativo que serializa o objeto e o aplicativo
que o desserializa devem definir a mesma enumeração em consideração.
•Ao serializar arrays, um elemento XML representa o array. Os elementos individuais da matriz formam
o elemento filho desse elemento. Os elementos individuais da matriz são incluídos em um
elemento, dependendo do tipo de dados da matriz.
•Durante a desserialização, o XmlSerializer cria uma matriz com o mesmo número de elementos
que os elementos serializados. Em seguida, ele atribui os valores dos elementos da matriz
de acordo.
Serialização e Herança
A serialização não se limita a tipos simples e complexos. É igualmente aplicável a classes herdadas. Suponha que você
tenha uma classe chamada Manager que herda de nossa classe Employee. Agora, quando você serializar Manager, todas as
propriedades públicas da classe base Employee e Manager serão serializadas. Isso também é verdade no caso de uma longa
cadeia de herança.
Para demonstrar como as classes herdadas são serializadas, precisamos adicionar uma classe chamada
Manager ao nosso aplicativo. A classe Manager herda da classe Employee (consulte a Listagem 8-1) e a estende adicionando
uma propriedade inteira NoOfSubordinates. A classe Manager é mostrada na Listagem 8-11.
221
Machine Translated by Google
O código cria uma classe chamada Manager que herda da classe Employee. Em seguida, adiciona um número inteiro
propriedade—NoOfSubordinates—para armazenar o número de subordinados de um gerente. Para acomodar a
propriedade adicional, a interface do usuário do aplicativo muda, conforme mostrado na Figura 8-6.
O aplicativo é quase o mesmo da Figura 8-1, mas há uma caixa de texto extra para aceitar o número de
subordinados do gerente. A Listagem 8-12 mostra o manipulador de eventos Click do botão Serialize.
222
Machine Translated by Google
if (checkBox1.Checked) {
Process.Start($"{Application.StartupPath}\\employee.xml"); }
O código é essencialmente o mesmo que usamos até agora, mas usa a classe Manager em vez da classe
Employee. Uma instância do Manager é criada e todas as suas propriedades são definidas. Em seguida, uma
instância de XmlSerializer é criada passando as informações de tipo da classe Manager. Por fim, a instância do
Manager é serializada chamando o método Serialize() de XmlSerializer. A Figura 8-7 mostra a saída XML resultante.
Observe como todas as propriedades públicas da classe base Employee, bem como a do Manager, são
serializado. O código para desserializar a classe Manager é muito semelhante ao que usamos anteriormente.
A Listagem 8-13 mostra esse código.
223
Machine Translated by Google
textBox4.Text = gerente.HomePhone;
textBox5.Text = gerenciador.Notas;
textBox6.Text = manager.NoOfSubordinates.ToString(); }
A única diferença nesse código é que ele usa Manager no processo de desserialização em vez de Employee.
usa automaticamente o nome dos membros públicos como os nomes dos elementos XML resultantes. Isso é o que é
necessário em muitos casos. No entanto, às vezes pode ser necessário customizar os dados XML serializados para atender
às suas necessidades. No exemplo anterior que ilustra a serialização de tipos complexos, obtivemos o documento XML
mostrado na Listagem 8-14.
<?xml versão="1.0"?>
<Employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://
www.w3.org/2001/XMLSchema"> <EmployeeID>1</EmployeeID > <FirstName>Nancy</
FirstName> <LastName>Davolio</LastName>
<HomePhone>(206) 555-9857</HomePhone>
<Notas>A educação inclui bacharelado em psicologia pela Colorado State University em 1970.
Ela também completou "The Art of the Cold Call". Nancy é membro do Toastmasters International.</Notes>
<Type>Permanente</Type>
<E-mails>
<string>nancy@localhost</string>
</E-mails>
<Endereço>
<Rua>507 - 20th Ave. E. Apt. 2A</Street> <City>Seattle</
City> <State>Washington</State> <Country>EUA</
Country> <PostalCode>98122</PostalCode> </Address>
</Empregado>
No entanto, e se você quiser que a estrutura XML resultante se assemelhe à Listagem 8-15?
<?xml version="1.0"?>
<MeuFuncionário xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/ XMLSchema" EmployeeCode="1"> <FName>Nancy</
FName> <LName>Davolio</LName>
224
Machine Translated by Google
<Observações>
A educação inclui bacharelado em psicologia pela Colorado State University em 1970. Ela também concluiu "The Art of the Cold
Call". Nancy é membro do Toastmasters International.
</Remarks>
<EmployeeType>Funcionário permanente</EmployeeType>
<EmailAddresses>
<Email>nancy@localhost</Email> </
EmailAddresses> <Endereço> <Rua>507 - 20th
Ave. E. Apt. 2A</Street> <City>Seattle</City>
<State>Washington</State> <Country>EUA</Country>
<PostalCode>98122</PostalCode> </Address> </MyEmployee>
•Os nomes dos elementos são totalmente diferentes dos nomes das propriedades públicas.
•Os endereços de e-mail são armazenados como elementos <Email> e não como elementos <string>.
• O valor da propriedade HomePhone não é serializado, mesmo que seja um membro público
da classe.
[XmlRoot(ElementName="MeuFuncionário")]
public class Funcionário {
[XmlIgnore]
public string HomePhone { get; definir; }
225
Machine Translated by Google
[XmlElement(ElementName = "Comentários")]
public string Notas { get; definir; }
[XmlArray(ElementName = "EmailAddresses")]
[XmlArrayItem(ElementName = "Email")]
public string[] E-mails { get; definir; }
[XmlElement(ElementName = "EmployeeType")]
public EmployeeType Type { get; definir; }
[XmlElement(IsNullable = true)]
endereço público Endereço { get; definir; } = novo Endereço();
}
[XmlEnum(Name="Funcionário Permanente")]
Permanente,
[XmlEnum(Name = "Empregado com contrato")]
Contrato
}
Vamos dissecar a listagem anterior passo a passo e ver o significado de cada atributo usado.
226
Machine Translated by Google
Por padrão, todos os membros públicos de uma classe são serializados, mas às vezes isso não é o que você deseja.
Por exemplo, se você estiver armazenando informações de cartão de crédito em uma propriedade pública, talvez não
queira serializá-las por razões óbvias de segurança. Um membro público pode ser ignorado durante o processo de
serialização decorando-o com o atributo [XmlIgnore]. Em nosso exemplo, a propriedade HomePhone está marcada com este atributo.
classe Employee tem uma propriedade chamada Emails que é do tipo string array. No esquema de
nomenclatura padrão, quando essa propriedade é serializada, um nó XML é criado com o nome Emails. Este
nó contém ainda nós filhos, cada um contendo o valor do elemento da matriz. Os nomes dos elementos filhos
são iguais ao tipo de dados do array (<string> em nosso exemplo). Você pode alterar esse comportamento com
a ajuda dos atributos [XmlArray] e [XmlArrayElement]. O primeiro marca os membros públicos que são tipos de
matriz e especifica o nome do elemento XML para o membro. O último atributo controla o nome do elemento
XML atribuído aos membros individuais da matriz. Em nosso exemplo, a propriedade Emails será serializada
como <EmailAddresses> e cada elemento da matriz será colocado dentro de um elemento <Email>.
tem uma propriedade Address que é um tipo de objeto. Se essa propriedade for nula, XmlSerializer ainda emite
um elemento XML vazio para ela, mas você pode usar o atributo [XmlElement] para alterar esse comportamento.
A propriedade booliana IsNullable do atributo [XmlElement] indica se o elemento XML vazio será emitido quando
o membro for nulo. Definir essa propriedade como true não emitirá o elemento XML vazio se a propriedade
Address for nula.
enumeração EmployeeType tem dois valores: Permanent e Contract. Por padrão, quando um membro do tipo
EmployeeType é serializado, o valor desses identificadores de enumeração é emitido no XML serializado.
O atributo [XmlEnum] especifica o valor alternativo para serializar em vez do nome real do identificador e é aplicado em
identificadores de enumeração. A propriedade Name do atributo [XmlEnum] especifica o texto que será serializado em vez
do nome do identificador.
•Cada membro da classe que você deseja serializar deve ser marcado com
Atributo [DataMember]. Membros não decorados com [DataMember] serão ignorados durante o
processo de serialização.
227
Machine Translated by Google
[DataContract]
public class Funcionário {
[DataMember]
public int EmployeeID { get; definir; }
[DataMember]
public string FirstName { get; definir; }
[DataMember]
public string LastName { get; definir; }
[DataMember]
public string HomePhone { get; definir; }
[DataMember]
string pública Notas { get; definir; }
}
O atributo [DataContract] adicionado à classe Employee indica que a classe Employee é um data
contrato e pode ser serializado por DataContractSerializer. O atributo [DataMember] adicionado às
propriedades da classe Employee indica que o membro em questão faz parte de um contrato de dados e pode ser
serializado pelo DataContractSerializer.
Em seguida, modifique o manipulador de eventos Click do botão Serialize, conforme mostrado na Listagem 8-18.
228
Machine Translated by Google
stream.Close(); if
(checkBox1.Checked) {
Process.Start($"{Application.StartupPath}\\employee.xml");
}
}
A maior parte do código mostrado na Listagem 8-18 deve parecer familiar para você, exceto as linhas marcadas em negrito. O
a primeira linha marcada em negrito cria uma instância de DataContractSerializer passando as informações de
tipo da classe Employee. A segunda linha chama o método WriteObject() de DataContractSerializer. O método
WriteObject() aceita dois parâmetros — um objeto de fluxo para gravar os dados e o objeto a ser serializado.
Se você executar o aplicativo e clicar no botão Serialize após preencher várias caixas de texto, o resultado
O documento XML deve se parecer com a Figura 8-8.
Para desserializar XML previamente serializado usando DataContractSerializer, você pode usar o método
ReadObject(). A Listagem 8-19 mostra o manipulador de eventos Click do botão Deserialize que ilustra o uso de
ReadObject().
stream.Close();
textBox1.Text = emp.EmployeeID.ToString();
textBox2.Text = emp.FirstName;
229
Machine Translated by Google
textBox3.Text = emp.LastName;
textBox4.Text = emp.HomePhone;
textBox5.Text = emp.Notas;
}
Observe o código marcado em negrito. A primeira linha cria uma instância de DataContractSerializer passando as
informações de tipo da classe Employee. A segunda linha então chama o método ReadObject() no DataContractSerializer. O
fluxo a ser lido é fornecido como parâmetro para ReadObject(). O ReadObject() lê um objeto, que é então convertido para o
tipo Employee.
Se você executar o aplicativo novamente e clicar no botão Deserialize, verá que todas as caixas de texto
estão preenchidos com os valores corretos.
observado, o DataContractSerializer usa o nome de classe e os nomes de propriedade por padrão para os elementos XML
resultantes. Você pode personalizar os nomes dos elementos XML especificando-os nos atributos [DataContract] e [DataMember].
A Listagem 8-20 mostra como isso é feito.
O atributo [DataContract] agora especifica sua propriedade Name como MyEmployee. Assim, o nome do elemento raiz será
MyEmployee. Os atributos [DataMember] adicionados às propriedades EmployeeID, FirstName, LastName e Notes definem suas
propriedades Name como EmployeeCode, FName, LName e Remarks, respectivamente. A propriedade HomePhone é decorada
com o atributo [IgnoreDataMember], indicando que HomePhone não será serializado.
A Figura 8-9 mostra um exemplo de XML gerado após a personalização que acabamos de discutir.
230
Machine Translated by Google
Como você pode ver, os nomes dos elementos XML agora estão de acordo com nossa propriedade Name. Além disso, o
HomePhone não foi serializado. Se você desserializar esse XML, descobrirá que todas as caixas de texto, exceto HomePhone, são
preenchidas conforme o esperado. A caixa de texto HomePhone permanece vazia por razões óbvias.
ÿ Observação A classe SoapFormatter usada nesta seção agora é considerada obsoleta. Discutimos isso aqui
porque alguns aplicativos mais antigos ainda podem estar usando. Além disso, dá a você a chance de se familiarizar
com o formato SOAP sem entrar nos detalhes dos serviços da Web ASMX ou dos serviços WCF.
SOAP é um padrão da indústria que forma um dos pilares dos serviços da web. Embora o SOAP seja usado
extensivamente junto com serviços da web, você pode usá-lo como um formato de codificação para serialização de objetos.
Ao serializar objetos usando a classe XmlSerializer, você não precisa fazer nada de especial para o
próprias aulas. No entanto, quando você deseja usar o SOAP como formato de serialização, deve marcar suas classes com o atributo
[Serializable]. Só então suas classes podem ser serializadas.
A classe SoapFormatter cuida de todas as complexidades da serialização de seus objetos no formato SOAP. A classe SoapFormatter
reside no namespace System.Runtime.Serialization.Formatters.Soap, que reside fisicamente no assembly
System.Runtime.Serialization.Formatters.Soap.dll.
Vamos revisitar o aplicativo que desenvolvemos quando começamos este capítulo (consulte a Figura 8-1) e modificá-lo para usar
SoapFormatter em vez de XmlSerializer. A interface do usuário do aplicativo permanece inalterada, mas a maneira como serializamos e
desserializamos os objetos é diferente.
Primeiro, você precisa marcar a classe Employee com o atributo [Serializable]. A classe Employee modificada é mostrada na
Listagem 8-21.
231
Machine Translated by Google
[Serializável]
public class Employee {
Como você pode ver, o atributo [Serializable] é um atributo de nível de classe. Portanto, ele é colocado no
topo da classe Employee e marcado como uma classe serializável. A Listagem 8-22 mostra o manipulador de
eventos Click do botão Serialize. Desta vez, o código usa a classe SoapFormatter.
O código cria uma instância da classe Employee e define suas propriedades com os valores inseridos nas
caixas de texto. Um objeto FileStream é então criado e cria um arquivo no qual os dados serializados devem ser
gravados. Em seguida, um objeto SoapFormatter é criado. O método Serialize() de SoapFormatter aceita dois
parâmetros: um fluxo no qual os dados serializados devem ser gravados e o objeto que deve ser serializado. A
contrapartida dessa operação é executada no manipulador de eventos Click do botão Deserialize e é mostrada na
Listagem 8-23.
232
Machine Translated by Google
O código abre um stream apontando para o mesmo arquivo para o qual o objeto foi serializado anteriormente. Uma instância
de SoapFormatter é então criado. O método Deserialize() de SoapFormatter lê o fluxo e desserializa o objeto. O valor de
retorno de Deserialize() é do tipo object e, portanto, é o tipo convertido para a classe Employee. Depois que o objeto Employee
é recuperado, seus valores de propriedade são atribuídos às caixas de texto correspondentes. Se você executar o aplicativo e
serializar o objeto Employee, deverá ver uma saída semelhante à Figura 8-10.
Como você pode ver, a saída XML agora está no formato SOAP. Também há menção de alguns namespaces
relacionados ao SOAP. Observe que os nomes dos elementos XML não são iguais aos nomes das propriedades; em vez disso,
os nomes dos elementos exclusivos são formados usando os nomes das propriedades. Ao contrário da serialização XML, que é
superficial por natureza, a serialização SOAP feita por meio da classe SoapFormatter é uma serialização profunda. Serializa
membros privados, protegidos e públicos de um objeto.
233
Machine Translated by Google
O primeiro método está disponível desde o .NET Framework 1.1. Este último método foi introduzido em
.NET 2.0. Em nosso exemplo, usaremos os dois métodos para customizar o processo de serialização.
Usaremos o mesmo aplicativo que desenvolvemos na seção anterior enquanto ilustramos o uso da classe SoapFormatter.
Suponha que você queira customizar os nomes dos elementos na mensagem SOAP resultante. Você também deseja controlar
quais propriedades estão sendo serializadas.
Por fim, suponha também que você deseja proteger os dados XML serializados de usuários casuais. Você deseja implementar
a codificação Base64 para os dados que estão sendo serializados para que leitores casuais não possam ler facilmente o conteúdo
do arquivo. Esses dados precisam ser codificados em um esquema de codificação Base64 e decodificados quando desserializados.
Abra a classe Employee que você criou anteriormente e implemente a interface ISerializable nela. como parte
da implementação, você precisa fazer duas coisas:
234
Machine Translated by Google
O construtor aceita os mesmos dois parâmetros do método GetObjectData(). A finalidade desse construtor é recuperar
valores serializados anteriormente e atribuí-los às propriedades.
Isso é feito usando o método GetValue() da classe SerializationInfo. O método GetValue() aceita o nome
associado ao valor a ser recuperado e seu tipo de dado. Ao recuperar o valor, ele é convertido e atribuído à propriedade
correspondente do objeto Employee que está sendo construído. Observe que a propriedade HomePhone recebe um valor de
string vazio, pois o ignoramos durante o processo de serialização.
Agora adicione duas funções auxiliares chamadas Encode() e Decode() ao aplicativo anterior, conforme mostrado na
Listagem 8-26.
Certifique-se de adicionar esses métodos auxiliares à classe Employee. A função Encode() aceita uma string que deve
ser codificada no formato Base64. Em seguida, ele converte a string em uma matriz de bytes usando o método GetBytes()
da classe ASCIIEncoding. A matriz de bytes é então alimentada ao método ToBase64String() da classe Convert, que retorna
uma string codificada em Base64 representando a matriz de bytes fornecida.
A função Decode() aceita uma string codificada em Base64 que deve ser decodificada de volta para uma
representação de string simples. Em seguida, ele chama o método FromBase64String() da classe Convert e passa a string
Base64 fornecida para ele. O método FromBase64String() retorna uma matriz de bytes representando a versão decodificada
da string fornecida. A matriz de bytes é convertida em uma string usando o método GetString() da classe ASCIIEncoding.
Agora precisamos adicionar quatro métodos à classe Employee, conforme mostrado na Listagem 8-27.
[OnSerializing]
public void OnSerializing(StreamingContext context) {
PrimeiroNome = Encode(PrimeiroNome);
Sobrenome = Encode(Sobrenome);
HomePhone = Codificar(HomePhone);
Notas = Encode(Notas);
}
[OnSerialized]
public void OnSerialized(StreamingContext context) {
PrimeiroNome = Decode(PrimeiroNome);
Sobrenome = Decode(Sobrenome);
HomePhone = Decode(HomePhone);
Notas = Decodificar(Notas);
}
235
Machine Translated by Google
[OnDeserializing] public
void OnDeserializing(StreamingContext context) {
[OnDeserialized]
public void OnDeserialized(StreamingContext context) {
PrimeiroNome = Decode(PrimeiroNome);
Sobrenome = Decode(Sobrenome);
HomePhone = Decode(HomePhone);
Notas = Decodificar(Notas);
}
Os quatro métodos são marcados com os atributos [OnSerializing], [OnSerialized], [OnDeserializing] e [OnDeserialized].
Esses atributos permitem personalizar o processo de serialização e desserialização usando métodos anteriores e posteriores:
236
Machine Translated by Google
Observe como todos os dados são serializados no formato Base64. Observe também como os nomes dos elementos são
escolhidos a partir dos valores especificados no método GetObjectData().
Se você clicar no botão Deserialize, verá que todas as caixas de texto, exceto HomePhone, estão
preenchido com os valores corretos. Isso ocorre porque ignoramos o HomePhone durante a serialização.
Resumo
Neste capítulo, examinamos o processo de serialização XML em detalhes. O próprio .NET Framework usa a serialização
em muitos lugares, incluindo serviços Web e serviços WCF. As três classes, ou seja, XmlSerializer, DataContractSerializer
e SoapFormatter, permitem que você serialize seus objetos em XML e SOAP. O XmlSerializer é usado pelos serviços Web
XML para realizar a serialização.
DataContractSerializer é usado pelos serviços WCF e API da Web para realizar a serialização.
A serialização XML feita usando XmlSerializer pode ser personalizada com a ajuda de vários atributos
como [XmlRoot] e [XmlElement]. A serialização XML feita usando DataContractSerializer pode ser personalizada com a ajuda de
atributos como [DataContract] e [DataMember]. A serialização SOAP feita usando SoapFormatter pode ser personalizada
implementando a interface ISerializable. Você também pode usar atributos como [OnSerializing] e [OnDeserialized] para controlar
o pré e o pós-processamento dos dados.
No próximo capítulo, você aprenderá sobre os serviços Web XML. Você também aprenderá mais sobre o
Protocolo SOAP e padrões relacionados.
237
Machine Translated by Google
CAPÍTULO 9
A ideia de desenvolvimento distribuído de aplicativos não é nova. Tecnologias distribuídas, como Distributed Component Object Model
(DCOM), Remote Method Invocation (RMI) e Common Object Request Broker Architecture (CORBA) existem há anos. No entanto,
nenhuma dessas tecnologias é um padrão inequívoco da indústria. É aí que entram os serviços baseados em padrões. Um serviço
expõe algumas funcionalidades como uma API (Application Programming Interface) que os clientes podem consumir. A transferência
de dados entre o cliente e o serviço geralmente ocorre em um formato conhecido por ambas as partes (como binário, SOAP, XML e
JSON).
O .NET Framework permite que você crie serviços usando três maneiras principais — Web Services (também chamados de
serviços da Web ASMX ou serviços da Web XML), Windows Communication Foundation (WCF) e API da Web.
Essas estruturas foram desenvolvidas em diferentes pontos no tempo com base nas tendências e demandas do setor
predominantes nesses respectivos momentos. Os serviços da Web permitem que você crie serviços baseados em SOAP sobre
HTTP. O WCF é uma estrutura genérica de desenvolvimento de serviços que pode lidar com vários canais de comunicação, como
TCP e HTTP, e formatos como binário, SOAP, XML e JSON. A API da Web é uma estrutura para construir serviços RESTful baseados em
HTTP. Este capítulo discute o primeiro tipo - os serviços da web. Os outros dois sabores são discutidos no próximo capítulo.
Deve-se notar que os serviços da Web agora são considerados uma forma mais antiga de desenvolver serviços. Embora o .NET
Framework e o Visual Studio ainda os suportem, os aplicativos modernos preferem o WCF e a API da Web aos serviços da Web. No
entanto, este capítulo ainda discute os serviços da Web porque isso permitirá que você se familiarize com os protocolos e padrões
baseados em XML, como SOAP e WSDL.
Lembre-se de que este capítulo não visa ensinar o desenvolvimento de serviços da Web em detalhes. Ele se concentra
principalmente na construção de serviços da Web ASMX baseados em dados básicos e no papel desempenhado pelo XML.
Especificamente, você aprende sobre os seguintes tópicos:
Geralmente, os componentes residem na mesma máquina que seu aplicativo. E se eles estiverem localizados em um servidor
separado? E se a rede envolvida não for uma LAN, mas a Internet? E se você quiser hospedar os componentes em uma caixa Unix e
consumi-los de uma máquina Windows? É aí que os serviços da web entram em cena.
Você pode pensar nos serviços da Web como componentes que residem em um servidor da Web, enquanto os aplicativos os
consomem em uma rede. Mais formalmente, os serviços da Web podem ser definidos como um conjunto de APIs programáveis que
podem ser chamadas em uma rede usando XML, SOAP e HTTP.
Os serviços da Web são um padrão do setor e nenhuma empresa possui serviços da Web. Os três padrões – XML,
SOAP e HTTP – são os pilares da infraestrutura de serviço da web. A seguir estão alguns pontos a serem lembrados sobre os serviços
da web:
•Os serviços da Web não fornecem nenhuma interface de usuário. Eles fornecem apenas funcionalidade ou
serviços ao seu aplicativo.
•Os serviços da Web usam o mesmo modelo de solicitação-resposta usado pelos aplicativos da Web.
•Toda a comunicação entre um serviço da Web e seu cliente ocorre em um texto simples
formatar.
•Os serviços da Web podem residir em qualquer servidor da Web, desde que o cliente tenha
conectividade de rede com esse servidor.
•Um serviço web e seu cliente podem ser desenvolvidos usando plataformas completamente diferentes.
Por exemplo, você pode desenvolver um serviço da Web usando o .NET Framework e consumi-
lo em um aplicativo Java.
•Os serviços da Web no .NET Framework também são chamados de serviços da Web ASMX devido à extensão
de arquivo usada pelos arquivos de serviço da Web (*.asmx).
As plataformas modernas de desenvolvimento de software geralmente introduzem o conceito de proxy durante a comunicação
remota. Um proxy é uma entidade que representa alguma outra entidade e finge para seu aplicativo cliente que o próprio proxy é o
serviço da Web real. Ao fazer isso, o proxy o protege de detalhes de programação de rede de baixo nível (como programação de
soquete, protocolos subjacentes, formatos de comunicação e segurança). Seu aplicativo cliente nunca se comunica diretamente com o
serviço da web. Toda a comunicação (solicitação e resposta) é roteada por meio do proxy.
Se o proxy quiser fingir que o próprio proxy é o serviço da web, ele deve se parecer com o serviço da web.
Isso requer a descrição detalhada ou metadados de um serviço da web. Os padrões de serviços da Web fornecem o que é conhecido
como Web Services Description Language (WSDL). WSDL é um dialeto XML que descreve o serviço da web, listando detalhes como as
funções expostas pelo serviço da web, seus parâmetros, tipos de dados e valores de retorno. O proxy é gerado usando este documento
WSDL de um serviço da web.
A extensão de arquivo usada pelos serviços da Web do .NET Framework é .asmx. Os arquivos .asmx do serviço da Web podem ter
arquivos code-behind como ASP.NET Web Forms.
240
Machine Translated by Google
Criando um WebService
Para criar um serviço Web usando o Visual Studio, você precisa criar um novo aplicativo Web chamado
EmployeeWebService e adicionar um serviço Web ASMX a ele. A Figura 9-1 mostra a caixa de diálogo New Project do Visual Studio.
Depois de selecionar ASP.NET Web Application como o tipo de projeto e clicar em OK, selecione Empty como o projeto
modelo. Certifique-se também de marcar a caixa de seleção Web Forms. Essa caixa de diálogo é mostrada na Figura 9-2.
Figura 9-2. Criando um projeto Web Forms usando o modelo de projeto vazio
241
Machine Translated by Google
Depois de criar o projeto, adicione uma pasta na pasta raiz do projeto chamada Services. Em seguida, clique com o botão
direito do mouse na pasta Serviços e escolha Adicionar ➤ Novo item. Isso abrirá a caixa de diálogo Adicionar novo item,
conforme mostrado na Figura 9-3.
Pesquise por serviço da web ou localize uma entrada de serviço da web na lista. Especifique o nome do serviço web como
EmployeeManager.asmx e clique no botão Adicionar.
Agora você deve ver o EmployeeManager.asmx, bem como seu arquivo code-behind — EmployeeManager.
asmx.cs. O arquivo .asmx contém a marcação mostrada na Listagem 9-1.
A diretiva @WebService especifica que este é um serviço da web. Observe que o atributo CodeBehind aponta
para o arquivo EmployeeManager.asmx.cs. O atributo Class especifica a classe do arquivo CodeBehind que contém a
funcionalidade do serviço da web. Se você abrir o arquivo EmployeeManager.asmx.cs, deverá ver algo semelhante à
Listagem 9-2.
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)] public class
EmployeeManager: System.Web.Services.WebService {
[WebMethod]
public string HelloWorld() {
242
Machine Translated by Google
Aqui temos uma classe chamada EmployeeManager que herda da classe System.Web.Services.WebService. Na verdade,
herdar da classe WebService não é obrigatório, mas isso lhe dará facilidades adicionais, como manutenção de estado. Dentro
desta classe temos um método público chamado HelloWorld(). O método por si só não contém nada de especial - você deve ter
escrito muitos desses métodos em seus próprios aplicativos. O que o torna especial, no entanto, é o atributo WebMethod, que torna
o método chamável pela Web, ou seja, o aplicativo cliente pode chamar esse método em uma rede. Sua classe pode conter
qualquer número de métodos públicos ou privados. No entanto, apenas os métodos públicos e decorados com o atributo WebMethod
podem ser chamados pela web.
Observe que a classe Service é decorada com os atributos [WebService] e [WebServiceBinding]. O atributo
[WebService] é usado para especificar algumas informações adicionais sobre o serviço da Web, como sua descrição e namespace. A
propriedade Namespace indica o namespace XML padrão a ser usado para o serviço Web XML. Os namespaces XML permitem
identificar exclusivamente elementos e atributos de um documento XML. Cada serviço da Web precisa ter um namespace XML exclusivo
para se identificar, de modo que os aplicativos clientes possam diferenciá-lo de outros serviços da Web. Por padrão, esse namespace é
definido como http://tempuri. org/, mas é recomendável alterá-lo para algum outro URI. Por exemplo, você pode usar o nome de domínio
de sua empresa como namespace. Observe que, embora muitas vezes os namespaces XML sejam URLs, eles não precisam apontar
para recursos reais na Web. O atributo [WebServiceBinding] especifica uma ligação WSDL usada por esse serviço. Você pode pensar
em uma ligação WSDL como uma interface que define um conjunto de operações de serviço da web. O atributo ConformsTo especifica
a especificação WSI (Web Services Interoperability).
A organização WS-I fornece diretrizes para interoperabilidade de serviços da Web em várias plataformas.
Execute o aplicativo e você deverá ver algo semelhante à Figura 9-4.
243
Machine Translated by Google
Você pode estar se perguntando por que nosso serviço da Web está exibindo essa interface do usuário quando
sabemos que os serviços da Web não possuem uma interface do usuário. Na verdade, esta não é uma interface de usuário do
serviço da web. Essa interface é chamada de página de ajuda do serviço da web e permite que você teste seus serviços da
web. Como os serviços da Web por si só não possuem uma interface de usuário, como você ou seus clientes os testarão para
ver se funcionam corretamente? Para ajudá-lo nesses casos, o ASP.NET gera essas páginas de ajuda automaticamente. Na
parte superior da página de ajuda, você verá um link intitulado Descrição do serviço. Basta clicar nele e você verá o WSDL do
seu serviço da Web (consulte a Figura 9-5).
Dê uma olhada na barra de endereço. Você vê o WSDL na string de consulta? É assim que você pode manualmente
recupere o WSDL de qualquer serviço da Web ASP.NET. Simplesmente anexe o WSDL no final da URL do serviço da
web e você obterá o WSDL. Clique no botão Voltar para retornar à página anterior. Você notará a lista de métodos da web
(operações). Clique no método da web HelloWorld. Você será levado para outra página de ajuda onde poderá executar este
método da web (consulte a Figura 9-6).
244
Machine Translated by Google
Antes de clicar no botão Chamar, dê uma olhada abaixo dele. Você deve ver a marcação, conforme mostrado
nas Listagens 9-3 e 9-4.
245
Machine Translated by Google
HTTP/1.1 200 OK
Tipo de conteúdo: texto/xml; conjunto de caracteres = utf-8
Comprimento do conteúdo: comprimento
</HelloWorldResponse> </
soap:Body> </soap:Envelope>
Esses dois blocos representam a solicitação SOAP sendo enviada ao serviço da web e a resposta SOAP sendo recebida do
serviço da web. Como você pode ver, a solicitação e a resposta SOAP consistem em uma tag chamada <soap:Envelope>. Dentro há
uma tag obrigatória chamada <soap:Body>. A tag Body contém os dados XML que estão sendo passados ou retornados. Pode haver
uma tag opcional, <soap:Header>, dentro da tag <soap:Envelope> que pode ser usada para passar dados arbitrários para o serviço da
web.
Agora clique no botão Invocar. A página de ajuda executará o método web e abrirá outra janela para
mostre a resposta do método da web (consulte a Figura 9-7).
Agora feche o navegador e retorne ao Visual Studio. Modifique o método HelloWorld(), conforme mostrado na Listagem 9-5.
[WebMethod]
string pública HelloWorld(nome da string) {
246
Machine Translated by Google
Aqui adicionamos um parâmetro de string ao método HelloWorld(). O método agora retorna Hello
concatenado com o nome fornecido. Execute o serviço da Web novamente. Desta vez, você deve ver uma página de ajuda, conforme
mostrado na Figura 9-8, para invocar o método da web.
ASP.NET gera automaticamente uma caixa de texto para você inserir o parâmetro. Claro, isso funciona apenas para tipos de
dados primitivos, como strings e inteiros. O ASP.NET não poderá fazer isso para parâmetros de array, objeto ou coleção.
247
Machine Translated by Google
Este exemplo usará a abordagem primeiro código do Entity Framework (EF) para executar as operações do banco de dados.
Muitos aplicativos modernos usam Entity Framework para tarefas de banco de dados. Usamos EF aqui apenas para economizar tempo e
simplificar nosso código. Se desejar, você pode usar o código ADO.NET em vez do Entity Framework.
ÿ Observação Embora esta seção não use nenhum conceito avançado do EF, supõe-se que você tenha
familiaridade básica com o código do Entity Framework primeiro. Se não for esse o caso, você pode considerar
aprender o básico do código EF antes de continuar com esta seção.
Certifique-se de que o projeto que você acabou de criar esteja aberto no Solution Explorer. Observe que existe a pasta Models na
raiz do projeto. Usaremos esta pasta para armazenar os modelos do Entity Framework. Clique com o botão direito do mouse na pasta
Models e selecione Add ÿ New Item no menu de atalho. Na caixa de diálogo Add New Item, escolha a entrada ADO.NET Entity Data
Model (consulte a Figura 9-9).
Especifique o nome do modelo como Northwind e clique no botão Adicionar. Isso abrirá um assistente, conforme mostrado na Figura
9-10.
248
Machine Translated by Google
Esta caixa de diálogo permite escolher como o contexto será criado. Selecione Code First na opção de banco de dados
e clique em Avançar.
A próxima caixa de diálogo (consulte a Figura 9-11) permite que você especifique as informações da string de conexão do banco de dados.
Vá em frente e insira esses detalhes de acordo com a configuração do banco de dados e as credenciais de segurança. Clique em Avançar
quando estiver pronto.
249
Machine Translated by Google
A próxima etapa do assistente (consulte a Figura 9-12) permite que você selecione uma ou mais tabelas de banco de dados que deseja usar.
Em nosso exemplo, você precisa apenas de uma tabela - Empregados. Selecione essa tabela e conclua o assistente.
A conclusão bem-sucedida do assistente adicionará dois arquivos à pasta Models — Northwind.cs e Employee.cs. O primeiro
arquivo contém uma classe que herda de DbContext e o último arquivo contém a classe de entidade Employee.
Como estamos interessados apenas nas cinco colunas — EmployeeID, FirstName, LastName, HomePhone e Notes — remova todas as
outras propriedades da classe de entidade Employee. A Listagem 9-7 mostra a classe Employee final.
A classe Employee contém cinco propriedades públicas que mapeiam para as respectivas colunas da classe Employees
mesa. Além disso, modifique a classe de contexto Northwind para corresponder à Listagem 9-8.
250
Machine Translated by Google
public Northwind() :
base("nome=Northwind")
{}
O contexto Northwind contém um único DbSet chamado Employees. Isso conclui nosso modelo EF e estamos prontos
para adicionar a funcionalidade CRUD ao serviço da Web EmployeeManager.
Agora adicionaremos cinco métodos da web ao serviço da web EmployeeManager. Esses métodos da web são
SelectAll(), SelectByID(), Insert(), Update() e Delete(). Esses métodos da web fazem a respectiva operação. Vamos
discuti-los um por um.
A Listagem 9-9 mostra o método da web SelectAll().
[WebMethod]
public List<Empregado> SelectAll() {
O método da web SelectAll() retorna uma lista de objetos Employee. Dentro, ele cria um objeto de contexto Northwind.
Em seguida, ele seleciona todos os objetos Employee usando o DbSet Employees, ordena-os em EmployeeID e então retorna
a lista para o chamador. Observe que SelectAll() — e todos os outros métodos — é decorado com o atributo [WebMethod].
[WebMethod]
public Employee SelectByID(int id) {
return db.Employees.Find(id);
}
}
O método da web SelectByID() aceita um parâmetro de ID inteiro que representa um EmployeeID e retorna um
objeto Employee correspondente a esse valor. Dentro, ele usa o método Find() no DbSet Employees para capturar o objeto
Employee com o ID correspondente.
Agora adicione o método da web Insert(), conforme mostrado na Listagem 9-11.
251
Machine Translated by Google
[WebMethod]
public string Insert(Employee obj) {
db.Employees.Add(obj);
db.SaveChanges(); return
"Funcionário adicionado com sucesso!";
}
}
O método web Insert() aceita um objeto Employee contendo os detalhes de um funcionário a ser adicionado ao
banco de dados e retorna uma mensagem de sucesso. Dentro, ele adiciona o obj ao DbSet Employees usando o método
Add(). O método SaveChanges() é chamado para persistir as alterações no banco de dados. Por fim, uma mensagem de
sucesso é retornada ao chamador.
Agora é hora de adicionar o método da web Update(), conforme mostrado na Listagem 9-12.
[WebMethod]
public string Update(Employee emp) {
db.Entry(emp).State = EntityState.Modified;
db.SaveChanges(); return "Empregado modificado com
sucesso!";
}
}
O método web Update() aceita um objeto Employee que precisa ser atualizado no banco de dados. Dentro, muda a
propriedade State da entidade para Modified. Isso é feito usando o método Entry() do contexto e a enumeração EntityState.
Como antes, SaveChanges() é chamado para salvar as alterações no banco de dados físico. Por fim, uma mensagem de
sucesso é retornada ao chamador.
Conclua o serviço da web EmployeeManager adicionando o método da web Delete(), conforme mostrado na Listagem 9-13.
[WebMethod]
public string Delete(int id) {
}
}
252
Machine Translated by Google
O método da web Delete() aceita um EmployeeID de um funcionário a ser excluído. Dentro, ele encontra o funcionário usando o
método Find() do Employees DbSet. Em seguida, ele remove esse funcionário do DbSet Employees usando o método Remove(). SaveChanges()
é chamado para salvar as alterações no banco de dados físico.
Por fim, uma mensagem de sucesso é enviada ao chamador.
Isso conclui o serviço da Web EmployeeManager. Execute-o no navegador para exibir sua página de ajuda. Esse
vez, você verá os cinco métodos da Web recém-adicionados, conforme mostrado na Figura 9-13.
Figura 9-13. Página de ajuda do serviço da Web listando todos os métodos da web CRUD
Embora você não possa invocar todos eles usando a página de ajuda (porque alguns deles precisam do objeto Employee como
parâmetro), você pode tentar executar os métodos da web SelectAll(), SelectByID() e Delete().
Um exemplo de resultado após a execução de SelectByID() para EmployeeID de 1 é mostrado na Figura 9-14.
Como você pode ver, os dados estão sendo enviados pela rede como um documento XML.
253
Machine Translated by Google
Fazer isso abrirá a caixa de diálogo Add Service Reference, conforme mostrado na Figura 9-16.
Clicar no botão Descobrir listará todos os serviços (incluindo EmployeeManager.asmx) na seção Serviços. Selecionar
EmployeeManager.asmx revelará sua URL na área Endereço. Se seu serviço for externo à solução, você deve ter digitado
manualmente sua URL aqui.
A caixa de texto Namespace permite especificar um nome de namespace para a classe de proxy que está sendo gerada.
Digite EmployeeManagerReference nesta caixa de texto e clique no botão OK. Isso gerará um proxy de “estilo WCF” para seu serviço
da Web e o colocará na pasta Service References (consulte a Figura 9-17).
254
Machine Translated by Google
Existe uma maneira alternativa de gerar o proxy por meio da mesma opção Adicionar referências de serviço - usando
Referência Web. Vamos ver como isso funciona. Abra a mesma caixa de diálogo Adicionar referência de serviço. Há um botão Avançado
localizado na parte inferior da caixa de diálogo. Clicar nele abre a caixa de diálogo Configurações de referência de serviço, conforme mostrado na
Figura 9-18.
255
Machine Translated by Google
Há um botão Adicionar referência da Web na parte inferior desta caixa de diálogo (na seção Compatibilidade).
Clicar nele abre outra caixa de diálogo—Adicionar referência da Web (consulte a Figura 9-19).
Figura 9-19. Usando a caixa de diálogo Adicionar referência da Web para gerar o proxy
Você pode navegar até o serviço da Web EmployeeManager, fornecer algum nome de referência da Web (nome do namespace)
e clicar no botão Adicionar referência. As referências da Web são armazenadas na pasta Referências da Web. Deve-se notar que a
referência da web é uma forma mais antiga de gerar o proxy. Você deve usá-lo somente quando a compatibilidade com versões anteriores
for necessária.
Independentemente da forma como você gerou o proxy (uma referência de serviço ou uma referência da Web), você sempre
pode atualizar um proxy clicando com o botão direito do mouse no Solution Explorer e selecionando a opção de menu de atalho Atualizar
referência do serviço ou Atualizar referência da Web, respectivamente . Observe também que a geração de um proxy também adiciona
determinadas marcações no arquivo de configuração do aplicativo. Você não deve adulterar essa configuração, a menos que tenha certeza
sobre as alterações que está fazendo.
Criando um formulário que chama os métodos da Web Para demonstrar como chamar
métodos da Web, você precisará criar um formulário que permita adicionar, modificar e excluir funcionários da tabela Empregados. O
formulário deve se parecer com a Figura 9-20.
256
Machine Translated by Google
O formulário exibe uma lista de EmployeeIDs existentes em uma lista suspensa. Se você selecionar um EmployeeID
na lista suspensa, seus detalhes, como nome, sobrenome, telefone residencial e anotações, serão exibidos nas
respectivas caixas de texto. Você pode então modificar os detalhes e clicar no botão Atualizar ou excluir esse funcionário
clicando no botão Excluir. Para adicionar um novo funcionário, você precisa preencher os detalhes nos respectivos campos
(EmployeeID é uma coluna de identidade e, portanto, será gerada automaticamente pelo banco de dados).
Clicar no botão Inserir salva os detalhes do novo funcionário no banco de dados. Uma mensagem de sucesso
retornada pelos métodos da web é exibida em um rótulo abaixo dos botões.
A lista suspensa é preenchida com uma lista de EmployeeIDs existentes no evento Load do formulário. Listagem 9-14
mostra como isso é feito.
comboBox1.Items.Add(emp.EmployeeID);
}
}
257
Machine Translated by Google
Em seguida, o código chama o método SelectAll() no proxy. O método SelectAll() do serviço web
retorna uma Lista de objetos Employee. Essa lista é recebida como um array no código do cliente. Você pode ajustar esse
comportamento ao gerar o proxy (a caixa de diálogo Configurações avançadas). Aqui vamos nos ater às configurações
padrão, pois são suficientes para demonstrar o funcionamento do serviço da web. Um loop foreach percorre a matriz e
preenche a lista suspensa com todos os valores EmployeeID.
Ao selecionar um EmployeeID, seus detalhes são preenchidos nas demais caixas de texto. Isso é feito no evento
SelectedIndexChanged da lista suspensa (consulte a Listagem 9-15) invocando o método da web SelectByID().
O código cria um objeto proxy como antes. Em seguida, ele chama o método SelectByID() no proxy passando o
EmployeeID selecionado. O método SelectByID() retorna um objeto Employee. Os detalhes do funcionário, como
Nome, Sobrenome, Telefone residencial e Notas, são preenchidos nas respectivas caixas de texto.
Para adicionar um novo funcionário, você precisa preencher todas as caixas de texto com os valores correspondentes e clicar no botão
Botão Inserir. O manipulador de eventos Click do botão Inserir chama o método da Web Insert(). Isso é mostrado na
Listagem 9-16.
O código cria um objeto proxy e um novo objeto Employee. Este objeto Employee representa um novo funcionário
a ser adicionado ao banco de dados. Propriedades como FirstName, LastName, HomePhone e Notes são definidas com os
valores inseridos nas respectivas caixas de texto. Por fim, o método Insert() é chamado no proxy para adicionar o funcionário.
O método Insert() retorna uma mensagem de sucesso (assumindo que não houve exceção) que é exibida em um Label.
Para modificar os detalhes de um funcionário, você precisa selecionar o EmployeeID na lista suspensa e, em seguida,
altere os valores das caixas de texto. Depois de adicionar as alterações, clicar no botão Atualizar tenta salvar os
detalhes modificados chamando o método da Web Update(). Isso é mostrado na Listagem 9-17.
258
Machine Translated by Google
O código é quase idêntico à operação de inserção discutida anteriormente. A única diferença é que ele escolhe um
EmployeeID na lista suspensa em vez da caixa de texto (já que um funcionário existente está sendo modificado). E o
método Update() é chamado no proxy após atribuir todas as propriedades do objeto Employee.
Remover um funcionário é simples. Tudo o que você precisa fazer é selecionar um EmployeeID na lista
suspensa e clicar no botão Excluir. O código que remove um funcionário é mostrado na Listagem 9-18.
O código simplesmente chama o método Delete() no proxy passando um EmployeeID a ser excluído.
Isso conclui o aplicativo cliente. Execute o aplicativo e teste se as operações CRUD estão funcionando conforme
o esperado. Para entender melhor o fluxo de execução, você pode definir pontos de interrupção em vários manipuladores
de eventos, bem como nos métodos da web.
Entendendo o SOAP
Na seção anterior, você aprendeu que SOAP é um protocolo leve baseado em XML que forma um dos blocos de construção
da infraestrutura de serviço da web. Você também aprendeu como as solicitações e respostas de serviços da Web são
codificadas no formato SOAP. Agora é hora de espiar dentro do SOAP com um pouco de detalhe. Dê uma olhada na Listagem 9-19.
259
Machine Translated by Google
Como você deve ter adivinhado, a Listagem 9-20 representa uma solicitação SOAP. Se você observar esta marcação cuidadosamente,
você descobrirá que a solicitação consiste em um envelope (<soap:Envelope>) e um corpo (<soap:Body>). Na verdade, uma solicitação
ou resposta SOAP pode conter quatro partes possíveis. Cada uma dessas partes é descrita na Tabela 9-1.
Papel Descrição
Envelope Envolve a solicitação ou resposta SOAP. É o elemento raiz da mensagem SOAP e é representado pela marca
de marcação <soap:Envelope>. Todas as mensagens SOAP devem ter um envelope.
Cabeçalho Partes opcionais de uma mensagem SOAP. Eles são usados para passar dados arbitrários de e para o serviço web e
seu cliente. Por exemplo, você pode usá-los para passar informações de autenticação para o serviço da web. Um
cabeçalho SOAP é representado pela marca de marcação <soap:Header>.
Corpo Uma parte obrigatória de uma mensagem SOAP. Ele inclui os dados reais de solicitação ou resposta em formato
XML. O corpo SOAP é representado pela marca de marcação <soap:Body>.
Falta Uma parte opcional de uma mensagem SOAP. Ele entra em cena sempre que há uma exceção de tempo de execução
no serviço da web. Os detalhes da exceção são incluídos na tag <soap:Fault> e enviados de volta ao aplicativo cliente.
você modificará o serviço Web EmployeeManager e o aplicativo cliente para usar cabeçalhos SOAP. Vamos supor que, para chamar
com sucesso o serviço web EmployeeManager, o cliente deve enviar uma chave secreta para o serviço web. Com cada chamada para
um método da web, o serviço da web precisa verificar a existência de uma chave válida.
Agora a questão é: como você passaria a chave secreta do cliente para o serviço? Uma opção seria incluí-lo na assinatura de
todos os métodos da web. Mas isso complicará os métodos da web e dificultará futuras alterações. Uma alternativa melhor é passar a chave
secreta por meio de um cabeçalho SOAP personalizado. O serviço da web autenticará uma chamada com base neste cabeçalho SOAP e
retornará os dados solicitados apenas se a chave for validada.
Para iniciar a modificação, adicione uma classe chamada EmployeeManagerHeader na pasta Serviços (você pode colocá-la em
qualquer outra pasta se desejar). Essa classe envolve a chave secreta e consiste em apenas uma única propriedade: ClientKey. A
Listagem 9-20 mostra a classe User concluída.
260
Machine Translated by Google
Observe que a classe EmployeeManagerHeader herda da classe base SoapHeader, que reside no namespace
System.Web.Services.Protocols e representa um cabeçalho SOAP básico. Todas as classes de cabeçalho SOAP
personalizadas devem herdar direta ou indiretamente da classe SoapHeader. Certifique-se de ter importado o
namespace System.Web.Services.Protocols antes de criar a classe User.
A classe User simplesmente contém uma propriedade string — ClientKey. Depois de criar a
classe EmployeeManagerHeader, modifique o serviço da Web EmployeeManager conforme mostrado na Listagem 9-21.
[WebMethod]
[SoapHeader("Header", Direction = SoapHeaderDirection.In)] public
List<Funcionário> SelectAll() {
if (Cabeçalho==nulo)
{
throw new SoapHeaderException("O cabeçalho SOAP não foi encontrado!", SoapException.
ClienteFaultCode);
}
if(Header.ClientKey != "KEY001") {
Examine a classe de serviço da web com cuidado. No topo declara uma variável do tipo
EmployeeManagerHeader. O método da web SelectAll() agora está decorado com um atributo adicional chamado
[SoapHeader]. É assim que você especifica informações sobre um cabeçalho SOAP.
O atributo [SoapHeader] especifica um parâmetro e uma propriedade. O primeiro parâmetro
especifica o nome do membro do cabeçalho SOAP que queremos usar. Em nosso exemplo, o nome da
variável EmployeeManagerHeader é Header e, portanto, é isso que passamos para o método web. Este
membro deve estar disponível publicamente na classe de serviço da web. A propriedade Direction indica a
direção do SoapHeader e é do tipo de enumeração SoapHeaderDirection. Os valores possíveis da enumeração
SoapHeaderDirection são os seguintes:
• In: A direção de In indica que o cabeçalho SOAP é passado do cliente para o serviço web.
261
Machine Translated by Google
• Falha: A direção da Falha indica que um cabeçalho SOAP deve ser enviado ao cliente
quando o método da web lança uma exceção.
Dentro do método web SelectAll(), verificamos se o cabeçalho SOAP é nulo. Se sim, isso indica que
a chave do cliente não foi enviada e, portanto, o código gera uma SoapHeaderException. A classe SoapHeaderException
é usada para representar um erro no cabeçalho SOAP. O primeiro parâmetro do construtor SoapHeaderException é a
mensagem de erro e o segundo parâmetro é o código de falha SOAP para a chamada do cliente.
O código então verifica a propriedade ClientKey do cabeçalho SOAP. Se a chave do cliente estiver incorreta,
um SoapException é levantado. A classe SoapException é usada para representar um erro no processamento
da solicitação SOAP. O construtor de SoapException usa os mesmos dois parâmetros da classe
SoapHeaderException.
ÿ Observação Para simplificar, o código usa um valor de string embutido em código para uma chave de cliente. Em um caso mais
realista, você verificaria a chave em algum armazenamento de dados. E a chave pode ser criptografada por motivos de segurança.
Além disso, adicionamos aqui o [SoapHeader] atribuído ao método da web SelectAll() . Repita o mesmo processo para todos os
Para consumir com êxito o serviço Web EmployeeManager que você acabou de modificar, você também precisa
modificar o aplicativo cliente. A Listagem 9-22 mostra como o cabeçalho SOAP exigido pelo método da web SelectAll()
pode ser transmitido do aplicativo cliente.
tentar
comboBox1.Items.Add(emp.EmployeeID);
}
} catch(Exceção ex) {
MessageBox.Show(ex.Message);
}
}
Antes de escrever este código, certifique-se de atualizar o proxy clicando com o botão direito nele e
selecionando a opção Atualizar referência do serviço. Isso é necessário porque agora alteramos o serviço da Web
para usar cabeçalhos SOAP. E o proxy deve conhecer esses detalhes.
262
Machine Translated by Google
Pare o aplicativo e reverta o código que define o cabeçalho como nulo. Defina a propriedade ClientKey como alguma chave
inválida. Execute o aplicativo novamente. Desta vez, a caixa de mensagem (consulte a Figura 9.22) deve informá-lo sobre o
lançamento da SoapException.
ÿ Observação No exemplo que acabamos de discutir, presumimos que você está usando uma referência de serviço. Se
você estiver usando uma referência da Web, o cabeçalho SOAP será transmitido de maneira um pouco diferente. Não
discutiremos essas diferenças aqui, mas você pode descobrir isso facilmente com a ajuda do Visual Studio IntelliSense.
263
Machine Translated by Google
[XmlRoot(ElementName ="FuncionárioRoot")]
classe parcial pública Funcionário {
[XmlIgnore]
public string HomePhone { get; definir; }
[XmlElement(ElementName = "Comentários")]
public string Notas { get; definir; }
}
É a mesma classe Employee, mas desta vez foram adicionados atributos como [XmlRoot] e [XmlElement] para
personalizar os nomes dos elementos XML. A propriedade HomePhone é decorada com [XmlIgnore] para ignorá-la do
processo de serialização.
Se você invocar o método da web SelectByID() usando a página de ajuda, deverá ver algo semelhante à Figura 9-23.
264
Machine Translated by Google
Como você pode ver, o objeto Employee serializado agora usa nomes de elementos como <EmployeeRoot>,
<FName>, <LName> e <Remarks>. Não há entrada HomePhone porque nós a ignoramos usando [XmlIgnore].
265
Machine Translated by Google
Se você observar essa marcação WSDL, poderá identificar seis partes principais do documento. Estas seis partes são
os elementos principais de qualquer documento WSDL e estão listados na Tabela 9-2.
Papel Descrição
tipos O elemento <wsdl:types> inclui todas as definições de tipo do serviço da web.
mensagem Os dados XML que estão sendo transportados entre o serviço da Web e seu cliente. O elemento
<wsdl:message> representa esta mensagem.
portType A seção <wsdl:portType> define um grupo de operações (métodos da web) expostos pelo serviço da web.
Você pode pensar nisso como uma interface que contém uma série de elementos <wsdl:operation>.
vinculativo O protocolo e formato usado pelo portType. Ele é representado pela marca de marcação <wsdl:binding>.
porta Um endpoint de uma comunicação de serviço da web. Ele é representado pela marca de marcação
<wsdl:port>.
serviço A coleção de uma ou mais portas. Ele é representado pela marca de marcação <wsdl:service>.
As mensagens
Você aprendeu anteriormente que a comunicação de serviço da Web funciona com base em um modelo de solicitação e resposta.
Uma solicitação de serviço da Web, bem como uma resposta, consiste em dados SOAP. Esses dados SOAP são chamados de
mensagem SOAP. Cada método da web tem uma mensagem que representa uma solicitação para o método da web e uma mensagem
que representa a resposta desse método da web. Assim, nosso método web SelectAll() terá duas mensagens:
• Da mesma forma, o nome da mensagem de resposta está no formato XXXXSoapOut, em que XXXX é o
nome do método da web.
Os elementos de mensagem WSDL fornecem uma lista consolidada de todas as mensagens expostas pelo serviço da web.
Os nomes de mensagem fornecidos por esta lista são usados em qualquer outro lugar no documento WSDL.
As definições de tipo
Cada mensagem em um serviço da Web possui uma estrutura ou esquema específico. Esse esquema é especificado pelo elemento
de tipos do documento WSDL. Se você observar a seção de tipos no WSDL mencionado anteriormente, descobrirá que ela especifica
um esquema para todas as mensagens. Você também notará que o tipo de dados dos parâmetros e valores de retorno são especificados
aqui. Esse esquema corresponde de perto ao esquema XSD que você aprendeu nos capítulos anteriores.
266
Machine Translated by Google
Os tipos de portas
Um serviço da Web consiste em uma ou mais operações. Em termos simples, uma operação é análoga a uma função ou método.
Cada operação possui uma mensagem de entrada (solicitação) e uma mensagem de saída (resposta). Todas as operações de um
serviço da Web estão listadas na seção portType. O nome da porta está no formato XXXXSoap, em que XXXX é o nome da classe
de serviço da web. O serviço web EmployeeManager tem cinco operações.
Cada operação consiste em duas mensagens — mensagem de entrada e mensagem de saída. Lembre-se de que as mensagens são
definidas na seção de mensagens.
A ligação
Uma ligação especifica o formato da mensagem e o protocolo para cada tipo de porta. Por exemplo, em nosso serviço da Web,
há uma ligação definida para o tipo de porta EmployeeManagerSoap. A ligação entre um tipo de ligação e porta é o atributo type do
elemento <wsdl:binding>. O nome da ligação está no formato XXXXSoap, em que XXXX é o nome da classe de serviço da web.
O serviço
Um serviço é um conjunto de portas e ligações. Uma porta de serviço da web é um terminal lógico para um serviço da web. Um
serviço tem o mesmo nome que a classe de serviço da web. Em nosso exemplo, o elemento service define uma porta chamada
EmployeeManagerSoap e a vincula à ligação EmployeeManagerSoap.
Um resumo do WSDL
Resumindo o que discutimos:
•Cada operação normalmente tem uma mensagem de solicitação e uma mensagem de resposta.
•Todas as operações expostas por um serviço da web são listadas na seção portType de
o WSDL.
•Para cada tipo de porta, um formato de transporte e protocolo precisam ser especificados. Isso é chamado
de vinculativo.
•A seção de serviço do WSDL define um terminal para o serviço da web chamado porta.
Resumo
Neste capítulo, você aprendeu sobre um dos recursos poderosos do .NET Framework: serviços da Web. Os serviços da Web são
um conjunto programável de APIs que podem ser chamados em uma rede usando padrões da indústria, como XML, SOAP e HTTP.
Os serviços da Web podem ser muito benéficos em áreas como integração de aplicativos, comunicação entre plataformas e
comunicação distribuída pela Internet.
Você aprendeu a criar serviços da web, um proxy para o serviço da web e um cliente que consome a web
serviço. Por fim, você deu uma espiada na estrutura interna do SOAP e WSDL.
267
Machine Translated by Google
CAPÍTULO 10
O Capítulo 9 apresentou os serviços da Web e você aprendeu a desenvolver aplicativos baseados em serviços. Os
serviços da Web são baseados em determinados padrões e protocolos, como SOAP, XML e WSDL. Eles são ótimos
quando se trata de desenvolver serviços baseados em padrões que são acessados na internet. No entanto, às vezes
suas necessidades são diferentes. Por exemplo, você pode querer desenvolver serviços para um aplicativo de intranet e
usar o formato binário sobre TCP por motivos de desempenho. Ou você deseja usar algum mecanismo de comunicação
altamente seguro que usa algumas estratégias de criptografia personalizadas. Os serviços da Web podem não atender a sua
finalidade nesses casos.
Reconhecendo a necessidade de preencher a lacuna entre várias tecnologias de componentes, a Microsoft desenvolveu
o que é conhecido como Windows Communication Foundation (WCF). O WCF é uma estrutura genérica que fornece
um modelo unificado para o desenvolvimento de componentes de software orientados a serviços. No WCF, você pode empregar
o mesmo software independentemente de estar usando-o na Internet ou em uma intranet. Você pode projetar e desenvolver
seu software inicialmente para redes TCP e depois usá-lo em HTTP. Isso pode ser alcançado sem alterações no código-fonte.
O que é mais interessante para nós é o fato de que, por padrão, o WCF usa XML (SOAP) como formato de transferência de
dados. Isso significa que os dados enviados entre o cliente e o servidor estão no formato XML (SOAP). Você também pode
personalizar como seus objetos são serializados na rede.
O WCF é uma estrutura de propósito geral para o desenvolvimento de serviços. Requer boa quantidade de configuração.
E muitos desenvolvedores acham essa configuração tediosa. Especialmente para aplicativos da Web em que o canal de
comunicação subjacente é HTTP e a transferência de dados está em algum formato baseado em texto (XML, SOAP, JSON
e assim por diante), o amplo conjunto de recursos e a configuração do WCF são desnecessários. Foi assim que a API da
Web surgiu. A API da Web é uma estrutura para criar serviços RESTful. A estrutura da API da Web é desenvolvida
especificamente tendo em mente os aplicativos da Web e o HTTP e oferece um modelo de programação simples para os
desenvolvedores.
Este capítulo apresenta os seguintes tópicos:
ÿ Observação Este capítulo não pretende ensinar os fundamentos do WCF e da API da Web. Ele se concentra mais em como o
XML está relacionado a essas estruturas de desenvolvimento de serviço. Se você não estiver familiarizado com o WCF e a API da
• Camada de canal: a parte do WCF que lida com a programação de rede de baixo nível.
As classes da camada de canal são usadas por classes de alto nível do WCF.
• Serviço: Um pedaço de software que responde à comunicação através de uma rede. Um
serviço tem um ou mais endpoints. A comunicação com o serviço é redirecionada para um desses
endpoints.
• Binding: Um endereço é apenas uma URL onde o serviço pode ser localizado. No entanto, isso não é
suficiente. Você também precisa conhecer o protocolo usado para comunicação, como TCP ou
HTTP. Isso é especificado com a ajuda de uma ligação. A ligação especifica o protocolo para
codificar a solicitação e a resposta, bem como o protocolo para transportá-los pela rede.
• Contrato: Um conjunto de operações que são expostas pelo serviço. Em outras palavras, um
contrato é um conjunto de operações disponíveis em um determinado endpoint. No nível do
código, um contrato é definido com a ajuda de uma interface.
• Tipo de serviço: Uma classe que implementa um contrato.
270
Machine Translated by Google
• Um host que publica os serviços expostos pelos tipos de serviço em uma rede
Criando o serviço
A criação de um serviço WCF requer as seguintes etapas:
3. Defina as estruturas de dados (se houver) para transportar dados do serviço para o cliente.
Agora que você tem uma breve ideia sobre o que está envolvido na criação de serviços WCF, vamos criar um serviço.
Comece criando um novo projeto do tipo WCF Service Library denominado EmployeeWCFService (consulte a Figura 10-1).
Em seguida, adicione uma interface ao projeto usando a caixa de diálogo Adicionar novo item e nomeie-a
como IEmployeeManager.
271
Machine Translated by Google
ÿ Observação Quando você cria um novo projeto do tipo WCF Service Library, o Visual Studio adiciona um
modelo para criar um novo serviço por padrão. Além disso, a caixa de diálogo Adicionar novo item do Visual Studio
também fornece um modelo para adicionar um novo serviço WCF. No entanto, para facilitar o entendimento, não usarei os
modelos de código padrão.
Importe o namespace System.ServiceModel na parte superior do arquivo de interface e adicione o código mostrado em
Listagem 10-1 na interface.
usando Sistema;
usando System.Collections.Generic; usando
System.Text; usando System.ServiceModel;
usando System.Data;
namespace EmployeeWCFService {
[ServiceContract]
interface pública IEmployeeManager {
[Contrato de Operação]
List<ContratoDadosFuncionário> SelectAll();
[Contrato de Operação]
EmployeeDataContract SelectByID(int id);
[OperationContract]
string Update(EmployeeDataContract emp); }
A interface IEmployeeManager atua como um contrato WCF. A interface IEmployeeManager define três
métodos: SelectAll(), SelectByID() e Update(). O método SelectAll() quando implementado retornará uma lista de todos
os funcionários da tabela Employees do banco de dados Northwind. O método SelectByID() quando implementado
retornará detalhes de um funcionário específico. A classe EmployeeDataContract é definida e explicada posteriormente
nesta seção. Observe duas coisas sobre o contrato WCF IEmployeeManager:
•A interface deve ser decorada com o atributo [ServiceContract]. Este atributo indica que a
interface decorada por ele é um contrato de serviço WCF.
Depois de definir um contrato, você precisa implementá-lo. Você faz isso criando uma classe que implementa a
interface do contrato. Você não precisa fazer nada de especial com o tipo de serviço além de implementar o contrato de
serviço.
272
Machine Translated by Google
usando Sistema;
usando System.Collections.Generic; usando
System.Text; usando System.ServiceModel;
usando System.Runtime.Serialization;
usando System.Data;
namespace EmployeeWCFService {
[DataContract]
public class EmployeeDataContract {
[DataMember]
public int EmployeeID { get; definir; }
[DataMember]
public string LastName { get; definir; }
[DataMember]
public string FirstName { get; definir; }
[DataMember]
public string HomePhone { get; definir; }
[DataMember]
string pública Notas { get; definir; }
}
}
ÿ Nota Você achará esse código familiar porque usou os atributos [DataContract] e [DataMember] no Capítulo
8 enquanto aprendia a serialização XML.
273
Machine Translated by Google
Nomeie e modele Northwind e clique em Adicionar. Isso iniciará um assistente. Escolha o código primeiro do banco de dados
(Figura 10-3) e simplesmente siga o assistente para gerar o contexto e as classes de entidade.
A execução bem-sucedida do assistente adicionará os arquivos Northwind.cs e Employee.cs à pasta Models. O primeiro arquivo
contém a classe de contexto, enquanto o último arquivo contém a classe de entidade Employee. Como essas classes são geradas
automaticamente para você, elas não são discutidas aqui.
A última etapa na criação desse serviço é implementar o contrato de serviço em uma classe chamada
EmployeeManager. A Listagem 10-3 mostra o código completo da classe EmployeeService.
274
Machine Translated by Google
EmployeeID = e.EmployeeID,
FirstName = e.FirstName,
LastName = e.LastName,
HomePhone = e.HomePhone,
Notes = e.Notes
,
return query.ToList();
}
EmployeeID = e.EmployeeID,
FirstName = e.FirstName,
LastName = e.LastName,
HomePhone = e.HomePhone,
Notes = e.Notes
};
return query.SingleOrDefault();
}
}
}}}
275
Machine Translated by Google
criou o tipo de serviço, é hora de pensar em hospedá-lo. Hospedar o serviço o tornará disponível em uma rede para que os aplicativos
clientes possam consumi-lo. Para hospedar o serviço, você tem várias opções. Alguns deles incluem:
Neste exemplo, usamos um aplicativo de console como host. Nas seções posteriores, você aprenderá a usar o IIS para hospedar
Serviços WCF.
Adicione um novo projeto do tipo aplicativo de console na mesma solução do serviço e nomeie-o
EmployeeWCFServiceHost. Consulte a Figura 10-4.
276
Machine Translated by Google
<system.serviceModel>
<serviços>
<service name="EmployeeWCFService.EmployeeManager"
behaviorConfiguration="EmployeeWCFServiceBehavior">
<endpoint address="EmployeeManager"
binding="basicHttpBinding"
contract="EmployeeWCFService.IEmployeeManager"> </
endpoint> </service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="EmployeeWCFServiceBehavior">
<serviceMetadata httpGetEnabled="True"/>
<serviceDebug includeExceptionDetailInFaults="true" /> </behavior>
</serviceBehaviors> </behaviors> </ system.serviceModel>
277
Machine Translated by Google
Agora abra o método Main() do aplicativo de console e digite o código mostrado na Listagem 10-5.
tentar
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
O código recupera o Type da classe de tipo de serviço usando a expressão typeof(). Em seguida, ele cria uma
instância da classe Uri apontando para a URL baseada em HTTP na qual o serviço será publicado. Observe como o número
da porta é definido como 8000. Você pode usar qualquer número de porta, mas certifique-se de que não seja usado por
nenhum outro aplicativo e não esteja bloqueado em uma rede.
Em seguida, uma instância da classe ServiceHost é criada. A classe ServiceHost hospeda o serviço
publicando o tipo de serviço nos URIs especificados. Observe que o construtor da classe ServiceHost usa uma matriz
de parâmetros de URIs. No nosso exemplo já passamos de dois, mas você pode passar mais se quiser. A seguinte
assinatura do construtor deixará claro:
...
}
O método Open() da classe ServiceHost é então chamado. Este método realmente hospeda o serviço
dependendo das informações de configuração. O serviço permanecerá publicado enquanto o aplicativo host
estiver ativo. É por isso que o método ReadLine() da classe Console é chamado. Ele mantém o aplicativo ativo até que o
usuário pressione a tecla Enter. Finalmente, o método Close() da classe ServiceHost é chamado. Isso conclui o aplicativo
host.
278
Machine Translated by Google
ÿ Observação Durante o desenvolvimento, você também pode usar o host de serviço WCF interno do Visual Studio,
ignorando completamente o aplicativo host. No entanto, para uma compreensão clara de todas as etapas envolvidas, não
usaremos o host integrado neste exemplo.
O aplicativo consiste em uma caixa de listagem contendo uma lista de todos os funcionários. Clicar em um
funcionário específico exibirá seus detalhes nas caixas de texto colocadas abaixo. Você pode alterar os detalhes como nome,
sobrenome, telefone residencial e notas. Clicar no botão Atualizar salvará as alterações no banco de dados. Uma mensagem
de sucesso também é exibida para o usuário usando um rótulo.
Antes de consumir o serviço, você deve criar um proxy para ele. Execute o aplicativo de host de serviço que você
desenvolveu anteriormente (certifique-se de executar o aplicativo de fora do Visual Studio para poder concluir a próxima
etapa). Em seguida, clique com o botão direito do mouse no projeto do aplicativo cliente e escolha a opção de menu
Incluir referência de serviço. Isso abrirá uma caixa de diálogo como a mostrada na Figura 10-6.
279
Machine Translated by Google
Insira o endereço de ponto de extremidade HTTP do serviço WCF (ou seja, http://localhost:8000/
EmployeeWCFService) e EmployeeWCFServiceReference como o nome do namespace para a classe proxy.
Isso criará uma referência de serviço para o serviço. Todas as referências de serviço adicionadas a um projeto são listadas
no Solution Explorer, conforme mostrado na Figura 10-7.
Adicionar uma referência de serviço também adicionará alguma marcação no arquivo de configuração do aplicativo do cliente. Agora
você pode chamar o serviço a partir do seu código de cliente. A Listagem 10-6 mostra o manipulador de eventos Load do formulário.
280
Machine Translated by Google
comboBox1.Items.Add(emp.EmployeeID);
} proxy.Close(); }
O código declara uma variável local—endpoint—para armazenar o nome do terminal de serviço. Você pode descobrir
o nome da configuração do terminal no arquivo de configuração do aplicativo do cliente.
O código então cria uma nova instância da classe proxy. Observe como a classe proxy é denominada
XXXXClient (EmployeeManagerClient em nosso caso), onde XXXX é o nome da classe de serviço original. No construtor
da classe proxy, você passa o nome da configuração do terminal desejado.
O código então chama o método SelectAll() no objeto proxy para recuperar os detalhes de todos os funcionários.
Observe que o método SelectAll() chamado no proxy retorna uma matriz de objetos EmployeeDataContract.
Um loop foreach então preenche os EmployeeIDs na caixa de combinação. Por fim, o canal de comunicação
subjacente é fechado chamando o método Close() do proxy.
Quando um usuário clica em qualquer um dos funcionários listados na caixa de combinação, os detalhes desse funcionário
devem ser exibidos nas outras caixas de texto. Isso é feito no manipulador de eventos SelectedIndexChanged da caixa de combinação
(consulte a Listagem 10-7).
O código é muito parecido com o que você viu anteriormente. Desta vez, ele chama o método SelectByID() no
proxy passando o EmployeeID selecionado. O método SelectByID() retorna um objeto EmployeeDataContract preenchido
com os detalhes necessários. Os detalhes como EmployeeID, FirstName, LastName, HomePhone e Notes são exibidos
nas respectivas caixas de texto.
Depois que os detalhes de um funcionário são exibidos nas caixas de texto, você pode alterá-los e clicar em
Atualizar para salvá-los no banco de dados. O manipulador de eventos Click do botão Update é mostrado na Listagem 10-8.
281
Machine Translated by Google
dados.Notas = textBox4.Text;
label6.Text=proxy.Update(data);
proxy.Close();
}
O código é direto — ele cria um objeto proxy como antes. Desta vez, ele também cria um novo objeto
EmployeeDataContract e o preenche com os detalhes modificados de um funcionário. O método Update() do proxy
é então chamado em uma tentativa de salvar as alterações no banco de dados. Um controle Label exibe a
mensagem de sucesso retornada pelo método Update().
codificou todas as três partes (serviço, host e cliente), vamos testá-las. Primeiro, compile todos os projetos da
solução. Em seguida, execute o aplicativo de console do host. Se tudo correr bem, você deverá ver um prompt de
comando como o mostrado na Figura 10-8.
282
Machine Translated by Google
Se você obtiver esta página, o serviço está hospedado corretamente. Clique no URL especificado na parte superior da página da web
e você deverá ver o WSDL do serviço, conforme mostrado na Figura 10-10.
283
Machine Translated by Google
Você achará esse mecanismo muito semelhante aos serviços da web, onde você recuperou o WSDL de um serviço
da web apenas especificando o parâmetro wsdl query string.
Agora execute o aplicativo cliente e você verá a caixa de combinação preenchida com a lista de todos os
funcionários. Clique nos nomes dos funcionários individuais e seus detalhes serão exibidos em várias caixas de texto. Teste
também se quaisquer modificações nos detalhes do funcionário são salvas no banco de dados após clicar no botão Atualizar.
anterior, usamos um aplicativo de console para hospedar nosso serviço. No entanto, você também pode usar o IIS (ou o
servidor Web de desenvolvimento do Visual Studio) para hospedar serviços WCF. Dessa forma, seu serviço é iniciado
automaticamente quando o aplicativo IIS é iniciado e você também obtém todos os recursos de segurança do IIS para seu serviço.
Para começar, adicione um novo projeto ASP.NET Web Application à solução atual (consulte a Figura 10-11).
Ao criar o projeto, selecione o modelo de projeto Vazio, conforme mostrado na Figura 10-12.
284
Machine Translated by Google
Dessa forma, você terá um projeto apenas com os itens básicos do projeto, como web.config. Em seguida, adicione
uma referência ao assembly EmployeeWCFService como você fez nos exemplos anteriores. Agora, adicione um novo
serviço WCF ao projeto usando a caixa de diálogo Add New Item (consulte a Figura 10-13).
285
Machine Translated by Google
Isso adicionará um novo arquivo .svc ao projeto e também seu code-behind. O arquivo .svc contém informações
sobre o serviço WCF sendo exposto. Você pode excluir o arquivo code-behind porque nosso serviço já está empacotado
em um assembly.
Em seguida, abra o arquivo EmployeeManager.svc no editor de texto do Visual Studio. Você descobrirá que o
arquivo .svc contém a diretiva @ServiceHost. Modifique esta diretiva conforme mostrado na Listagem 10-9.
A diretiva @ServiceHost indica que o arquivo é um host de serviço WCF. O atributo Service especifica
o nome totalmente qualificado do tipo de serviço. Agora adicione um arquivo web.config ao site e digite a marcação
mostrada na Listagem 10-10.
<system.serviceModel>
<services> <service
name="EmployeeWCFService.EmployeeManager"
behaviorConfiguration="EmployeeWCFServiceBehavior"> <endpoint
address="" binding="basicHttpBinding"
contract="EmployeeWCFService.IEmployeeManager"> </
endpoint> </service> </services> <behaviors> <serviceBehaviors>
<behavior name="EmployeeWCFServiceBehavior" > <serviceMetadata
httpGetEnabled="True"/> <serviceDebug includeExceptionDetailInFaults="true" />
</behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
É essencialmente a mesma marcação que você especificou para o host do console. A única diferença é que o atributo
de endereço do elemento <endpoint> é uma string vazia. Isso ocorre porque, para um serviço hospedado no IIS, o endereço é
o mesmo que o URI do arquivo .svc que hospeda o serviço.
Para acessar o EmployeeService hospedado no IIS, a URL do terminal será http://localhost:49303/
EmployeeManager.svc (altere o número da porta para corresponder ao seu). Observe que a URL aponta para o arquivo .svc.
Você também pode anexar o parâmetro de string de consulta wsdl para extrair seu WSDL. A Figura 10-14 mostra um exemplo
de execução do arquivo .svc em um navegador.
286
Machine Translated by Google
Você pode alimentar este URL para o gerador de proxy e o proxy será gerado para você. o geral
processo de criação de proxy e chamada do serviço permanece o mesmo do exemplo anterior.
ÿ Observação A comunicação baseada em SOAP pode causar uma penalidade de desempenho devido à sua natureza
volumosa. No entanto, SOAP é uma escolha natural quando a interoperabilidade é mais importante para seu aplicativo. O WCF
também permite que você use comunicação binária sobre TCP. Essa comunicação pode oferecer benefícios de desempenho ao seu aplicativo.
Apenas para entender o que acabou de ser dito, vá para o projeto EmployeeWCFService e pressione F5. Fazê-lo vai abrir o
WCF Test Client, conforme mostrado na Figura 10-15.
287
Machine Translated by Google
O cliente de teste WCF permite invocar métodos de serviço. No lado esquerdo, clique duas vezes no método SelectAll().
Em seguida, clique no botão Chamar no lado direito do painel. Isso invocará o método e todos os objetos EmployeeDataContract
serão retornados. Alterne para a guia XML e você verá a solicitação SOAP e as mensagens de resposta SOAP.
No caso de serviços WCF RESTful (você aprenderá sobre eles em seções posteriores), os dados são serializados como XML
simples em vez de uma mensagem SOAP.
•Você também pode instruir o WCF a usar XmlSerializer para a serialização XML. Isso lhe dará mais controle
sobre como os dados são serializados. Você pode personalizar o processo de serialização usando
vários atributos como [XmlAttribute] e [XmlElement]. Você já aprendeu sobre a classe XmlSerializer no
Capítulo 8.
Para instruir a estrutura WCF de que deve usar XmlSerializer em vez de DataContractSerializer, tudo o que você precisa fazer é
decorar a interface do contrato de serviço com o atributo [XmlSerializerFormat]. A interface IEmployeeManager após a aplicação do
atributo [XmlSerializerFormat] é mostrada na Listagem 10-11.
288
Machine Translated by Google
[Contrato de serviço]
[XmlSerializerFormat]
interface pública IEmployeeManager {
....
....
}
•Os serviços REST fazem solicitações HTTP (usando verbos HTTP significativos, como GET, POST,
PUT e DELETE) para buscar e enviar dados.
No que diz respeito ao .NET Framework, há duas maneiras de criar serviços RESTful — serviços WCF REST e
Web API. Hoje em dia é mais comum que aplicações web utilizem JSON (JavaScript Object Notation) como formato
para transferência de dados entre um serviço e seu cliente, mas você também pode utilizar XML. Nosso interesse, é claro, é
ver como o XML pode ser usado junto com essas estruturas.
Os verbos HTTP como GET, POST, PUT e DELETE indicam a ação desejada a ser executada em um recurso. Se
um serviço executa operações de criação, leitura, atualização e exclusão (CRUD) em um banco de dados, você pode usar
POST para indicar uma operação INSERT, GET para indicar uma operação SELECT, PUT para indicar uma operação
UPDATE e DELETE para indicar uma operação DELETE Operação. No entanto, apenas usar um verbo específico não
imporá um tipo específico de operação. Cabe a você implementar esses verbos em um serviço dependendo dos requisitos
do aplicativo.
Agora que você tem uma ideia do que são serviços REST, verá como criá-los, primeiro usando WCF e depois usando
Web API.
289
Machine Translated by Google
•Como os serviços REST usam verbos HTTP para invocar métodos, você usa webHttpBinding.
Também é comum hospedar tal serviço no IIS ou em um servidor web (conforme discutido anteriormente).
Além dessas mudanças, as partes restantes de um serviço são mais ou menos as mesmas de antes.
Comece criando um novo aplicativo Web ASP.NET com base no modelo de projeto vazio. Assim como você fez
com o projeto EmployeeWCFService, adicione uma pasta Models e gere o contexto Entity Framework e a classe de
entidade (Employee). Adicione também a classe EmployeeDataContract como antes.
Em seguida, adicione uma interface — IEmployeeManager — ao projeto. A interface IEmployeeManager concluída é
mostrada na Listagem 10-12.
[ServiceContract]
interface pública IEmployeeManager {
[Contrato de Operação]
[WebGet(UriTemplate = "/funcionários",
ResponseFormat =WebMessageFormat.Xml)]
List<ContratoDadosFuncionário> SelectAll();
[Contrato de Operação]
[WebGet(UriTemplate = "/funcionários/{id}",
ResponseFormat = WebMessageFormat.Xml)]
EmployeeDataContract SelectByID(string id);
[Contrato de Operação]
[WebInvoke(Método = "POST",
UriTemplate = "/funcionários",
RequestFormat =WebMessageFormat.Xml ,
ResponseFormat = WebMessageFormat.Xml)]
string Insert(EmployeeDataContract emp);
[Contrato de Operação]
[WebInvoke(Method = "PUT",
UriTemplate = "/employees/{id}",
RequestFormat =WebMessageFormat.Xml,
ResponseFormat = WebMessageFormat.Xml)]
string Update(string id,EmployeeDataContract emp);
[Contrato de Operação]
[WebInvoke(Método = "EXCLUIR",
UriTemplate = "/employees/{id}",
ResponseFormat = WebMessageFormat.Xml)]
string Delete(string id);
}
290
Machine Translated by Google
•Method: A propriedade Method indica o verbo HTTP que invoca uma operação.
Os verbos comuns são GET, POST, PUT e DELETE.
•UriTemplate: indica o modelo da URL do endpoint que é usado para invocar um método. Por
exemplo, http://localhost/funcionários ou http://localhost/funcionários/1. Observe como o
EmployeeID é parametrizado usando a sintaxe {id}.
Este valor de id é considerado como uma string e, portanto, todas as operações subjacentes
especificam o parâmetro id como um tipo de string.
•RequestFormat: Indica o formato dos dados que acompanham um pedido. Os valores possíveis
são Xml e Json (enumeração WebMessageFormat).
•ResponseFormat: Indica o formato dos dados que acompanham uma resposta. Possível
os valores são Xml e Json (enumeração WebMessageFormat).
Agora que a interface IEmployeeManager está pronta, adicione um serviço WCF ao projeto chamado
EmployeeManager.svc. O arquivo code-behind do arquivo .svc abrigará a classe EmployeeManager. Essa classe
implementa a interface IEmployeeManager como antes. O código completo da classe EmployeeManager é mostrado na
Listagem 10-13.
EmployeeID = e.EmployeeID,
FirstName = e.FirstName,
LastName = e.LastName,
HomePhone = e.HomePhone,
Notes = e.Notes
,
return query.ToList();
}
}
291
Machine Translated by Google
EmployeeID = e.EmployeeID,
FirstName = e.FirstName,
LastName = e.LastName,
HomePhone = e.HomePhone,
Notes = e.Notes
};
return query.SingleOrDefault();
}
}
}
}
292
Machine Translated by Google
{
Employee emp = db.Employees.Find(int.Parse(id));
db.Employees.Remove(emp); db.SaveChanges(); return
"Funcionário deletado com sucesso!";
}
}
}
Esta implementação deve ser familiar para você porque SelectAll(), SelectByID() e Update() são
bastante semelhante ao exemplo anterior.
O método Insert() aceita um novo objeto EmployeeDataContract e constrói um novo funcionário a partir dele.
Em seguida, adiciona o funcionário recém-criado ao DbSet Employees. O método SaveChanges() salva os dados no
banco de dados.
O método Delete() aceita um EmployeeID a ser excluído. Lá dentro, ele encontra esse funcionário e o remove do
DbSet Employees usando o método Remove(). O método SaveChanges() salva as alterações no banco de dados.
<system.serviceModel>
<serviços>
<service name="EmployeeWCFServiceREST.EmployeeManager"
behaviorConfiguration="EmployeeWCFServiceRESTBehavior">
<endpoint address="" binding="webHttpBinding"
contract="EmployeeWCFServiceREST.IEmployeeManager"
behaviorConfiguration="web"> </endpoint> </service>
</services>
<comportamentos>
<serviceBehaviors>
<behavior name="EmployeeWCFServiceRESTBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /> <serviceDebug
includeExceptionDetailInFaults="false" /> </behavior>
<comportamento>
</serviceBehaviors>
<endpointBehaviors>
<nome do comportamento="web">
<webHttp/>
</behavior>
</endpointBehaviors> </
behaviors>
</system.serviceModel>
293
Machine Translated by Google
Essa configuração é bastante semelhante à usada nos aplicativos de host anteriores. A principal diferença é que ele usa
a associação webHttpBinding em vez de basicHttpBinding.
Isso conclui o serviço REST EmployeeManager. E agora é hora de consumi-lo de um aplicativo cliente.
O aplicativo cliente é bastante semelhante ao que você desenvolveu no exemplo anterior. Mas agora ele tem mais dois botões
- Inserir e Excluir - para invocar as respectivas operações. Inserir detalhes de um novo funcionário, exceto EmployeeID, e clicar no
botão Inserir adiciona esse funcionário. Como EmployeeID é uma coluna de identidade, não é necessário especificá-la na interface do
usuário. Selecionar um EmployeeID na caixa de combinação e clicar no botão Excluir exclui esse funcionário. A mensagem de sucesso
retornada das operações de inserção, atualização e exclusão é exibida em um controle Label colocado abaixo dos botões.
Embora a interface do usuário do aplicativo cliente seja semelhante ao exemplo anterior, o funcionamento interno
são totalmente diferentes. Anteriormente, você criava um proxy no aplicativo cliente e, em seguida, chamava os métodos desejados
no proxy. Tudo bem, já que o serviço era baseado em operações. Agora, esse aplicativo cliente deseja consumir um serviço REST.
Aqui, os verbos HTTP decidem quais métodos do serviço devem ser invocados.
Portanto, nenhuma geração de proxy é necessária. Em vez disso, esse aplicativo usará o componente HttpClient do namespace
System.Net.Http. Vamos ver como.
O manipulador de eventos Load do formulário é mostrado na Listagem 10-15.
294
Machine Translated by Google
comboBox1.Items.Add(item.InnerText);
}
}
O código declara uma variável HttpClient — cliente — para chamar o serviço REST. Como queremos chamar os serviços
de vários locais, esse objeto é criado no nível do formulário, em vez de uma variável local do manipulador de eventos.
O manipulador de eventos Load instancia o HttpClient e define sua propriedade BaseAddress para a URL base,
onde o serviço REST está localizado. No nosso caso, esta é a URL do aplicativo da web onde adicionamos o arquivo
EmployeeManager.svc. Certifique-se de alterar o número da porta conforme sua configuração. Você pode obter facilmente
esse URL visualizando o arquivo .svc em um navegador.
A próxima linha adiciona o cabeçalho Accept à coleção DefaultRequestHeaders. O valor do cabeçalho Accept de
application/xml significa que o serviço deve retornar dados no formato XML.
Em seguida, o código chama o SelectAll() do serviço. Observe que em nenhum lugar o nome da operação é
mencionado. O código usa o método GetAsync() do objeto HttpClient e especifica a URL do ponto de extremidade do serviço.
Lembre-se de que IEmployeeManager especifica este UriTemplate. O método GetAsync() usa o verbo GET para fazer a
solicitação. Todos os métodos de HttpClient são assíncronos por natureza. Acessar a propriedade Result bloqueia o thread atual
até que o método seja concluído.
O método GetAsync() retorna HttpResponseMessage — um objeto que envolve o valor de retorno real.
Para pegar o valor real, você usa o método ReadAsStringAsync(). O método ReadAsStringAsync() retorna a representação
XML dos dados retornados pelo serviço REST. Esse fragmento XML é então carregado em um XmlDocument. Como queremos
preencher a caixa de combinação com valores EmployeeID, todos os elementos <EmployeeID> são recuperados usando o
método GetElementsByTagName(). Um loop foreach percorre os elementos recuperados e preenche a caixa de combinação
conforme desejado.
Se você observar a variável de string xmlData na janela Quick Watch do Visual Studio, verá XML
conforme mostrado na Listagem 10-16.
295
Machine Translated by Google
<LastName>Davolio</LastName>
<Notes>A educação inclui bacharelado em psicologia pela Colorado State University em 1970. Ela
também concluiu "The Art of the Cold Call". Nancy é membro da Toastmasters International.</Notes> </
EmployeeDataContract>
....
....
</ArrayOfEmployeeDataContract>
ÿ Observação Aqui usamos o XML bruto retornado pelo serviço WCF REST. Se desejar, você pode carregar esses
dados XML em objetos Employee/EmployeeDataContract no aplicativo cliente (você precisará criar essas classes no
aplicativo cliente). Dessa forma, o aplicativo cliente pode processar esses objetos conforme necessário. Não faremos
isso aqui porque nosso interesse principal é ver como o formato de dados XML é usado por essas estruturas.
Ok, agora vamos ver como SelectByID() pode ser chamado no manipulador de eventos SelectedIndexChanged da caixa de
combinação (consulte a Listagem 10-17).
Este código é semelhante à listagem anterior. No entanto, o método GetAsync() também passa
um EmployeeID na URL. Desta forma, o método SelectByID() do serviço REST é chamado. O código restante
simplesmente lê os dados XML retornados e preenche várias caixas de texto com valores FirstName, LastName,
HomePhone e Notes.
O manipulador de eventos Click do botão Insert é responsável por chamar o método Insert() do REST
serviço. Esse manipulador de eventos é mostrado na Listagem 10-18.
296
Machine Translated by Google
Configuramos o serviço REST para usar o formato XML para a solicitação e também para a resposta. Enquanto
adicionando um funcionário, primeiro precisamos formar um fragmento XML que representa um objeto
EmployeeDataContract. Isso é feito criando um fragmento XML conforme mostrado. O fragmento XML basicamente
cria um elemento <EmployeeDataContract></EmployeeDataContract> com quatro elementos filhos — <FirstName>,
<LastName>, <HomePhone> e <Notes>. Os valores desses elementos filho são definidos nas respectivas caixas de
texto. Como você pode ver, o nome do elemento raiz corresponde ao nome da classe de contrato de dados e os nomes
dos elementos filhos correspondem aos nomes dos membros de dados.
Em seguida, o fragmento XML é agrupado em um objeto StringContent. E então o método PostAsync ()
de HttpClient é chamado. O método PostAsync() faz uma solicitação POST para o serviço especificado. O objeto
StringContent contendo os detalhes do funcionário também acompanha a solicitação.
O método Insert() do serviço retorna uma mensagem de sucesso. Esta mensagem é descompactada de
HttpResponseMessage usando o método ReadAsStringAsync(). Essa mensagem é então exibida em um controle
Label.
O manipulador de eventos Client do botão Update é semelhante e é mostrado na Listagem 10-19.
297
Machine Translated by Google
A principal diferença aqui é que o método PutAsync() de HttpClient foi chamado. Fazer isso faz um
Solicitação PUT ao serviço. A URL de solicitação também inclui o EmployeeID a ser modificado.
Finalmente, o manipulador de eventos Click do botão Delete faz o trabalho de chamar o método Delete() do
serviço e é mostrado na Listagem 10-20.
Aqui, o código chama o método DeleteAsync() de HttpClient. O EmployeeID a ser excluído é passado como parte da URL.
Isso conclui o aplicativo cliente. Execute o cliente e veja se a caixa de combinação é preenchida com os valores EmployeeID
existentes. Tente também adicionar, modificar e excluir funcionários para confirmar o funcionamento do serviço REST
EmployeeManager.
Comece criando um novo aplicativo da Web ASP.NET com base no modelo de projeto da API da Web (consulte
a Figura 10-17).
298
Machine Translated by Google
O projeto recém-criado contém um serviço de API da Web. Renomeie esse serviço (arquivo e classe) para
EmployeeManagerController. Em seguida, gere o modelo Entity Framework para a tabela Employees do banco de dados
Northwind, como você fez nos exemplos anteriores.
Abra o EmployeeManagerController no Editor do Visual Studio. Você notará que a classe
EmployeeManagerController herda de ApiController. Em seguida, escreva as ações que executam as operações CRUD
no EmployeeManagerController. O esqueleto dessas ações é mostrado na Listagem 10-21.
[HttpGet]
public List<Funcionário> SelectAll() { }
[HttpGet]
public Employee SelectByID(int id) { }
[HttpPost]
public string Insert(Employee obj) { }
299
Machine Translated by Google
[HttpPut]
public string Update(int id, Employee obj) { }
[HttpDelete]
public string Delete(int id) { }
•O serviço Web API usa objetos Employee. Não criamos um contrato formal de dados aqui. Você
poderia ter feito isso se necessário, mas na API da Web você não precisa de atributos como
[DataContract] e [DataMember].
•As ações da API da Web são decoradas com determinados atributos que as mapeiam para um verbo
HTTP. Por exemplo, o [HttpPost] adicionado à ação Insert() indica que as solicitações POST
serão mapeadas para a ação Insert(). Você pode evitar decorar as ações com esses atributos se
seguir a convenção de nomenclatura padrão — o nome da ação começa com o verbo HTTP com
o qual ela lida. Por exemplo, Get() é mapeado automaticamente para o verbo GET, Post() é
mapeado automaticamente para o verbo POST e assim por diante.
O código interno dessas ações é muito semelhante ao serviço EmployeeManager WCF. Então, não vamos
discutir todos eles aqui. Por uma questão de clareza, apenas SelectAll() e Post() são mostrados na Listagem 10-22.
[HttpGet]
public List<Empregado> SelectAll() {
}
}
[HttpPost]
public string Insert(Employee obj) {
300
Machine Translated by Google
db.Employees.Add(emp);
db.SaveChanges(); return
"Funcionário adicionado com sucesso!";
}
}
Ao contrário do WCF, a API da Web não exige que nenhuma configuração de host seja adicionada ao web.config. esse é o
benefício do modelo de programação simples oferecido pela API da Web.
Embora o aplicativo cliente seja bastante semelhante ao que você desenvolveu anteriormente, há duas diferenças:
• A URL do terminal para o serviço REST agora mudará e seguirá a API da Web
convenções.
•Como a API da Web agora está retornando e aceitando objetos Employee, os dados XML recebidos
e enviados do cliente serão agrupados em um elemento <Employee>.
Para entender como essas alterações são refletidas no código, a Listagem 10-23 mostra o manipulador de eventos Load
do formulário.
301
Machine Translated by Google
Listagem 10-23. O manipulador de eventos Load recebe objetos de funcionários no formato XML
comboBox1.Items.Add(item.InnerText);
}
Observe as linhas marcadas em negrito. A propriedade BaseAddress agora aponta para o endereço da API Web
aplicativo. Você deve alterar o número da porta conforme sua configuração.
A segunda linha em negrito especifica a URL da API da Web. Observe que a URL assume o formato /api/
<api_controller>. Esse padrão vem da rota padrão da API da Web e você pode alterá-lo no arquivo WebApiConfig.cs do
projeto da API da Web.
Se você observar o xmlData retornado da API da Web, deverá ver algo como isto:
Ela também completou "The Art of the Cold Call". Nancy é membro da Toastmasters International.</Notes> </
Employee>
....
</ArrayOfEmployee>
Como você pode ver, os detalhes do funcionário agora estão agrupados dentro de um elemento <Employee>.
Isso também significa que durante as operações POST e PUT, você deve enviar os dados agrupados dentro de
um elemento <Employee>. Por exemplo, considere o código mostrado na Listagem 10-24, que mostra o manipulador de
eventos Click do botão Atualizar.
302
Machine Translated by Google
Os detalhes modificados do funcionário agora estão agrupados dentro do elemento <Employee> junto com os
detalhes do namespace.
Outros manipuladores de eventos não são discutidos aqui, pois são bastante semelhantes aos exemplos anteriores.
Apenas tome cuidado com essas duas diferenças e complete-as por conta própria (ou pegue-as no código-fonte do livro).
Depois que o aplicativo cliente estiver pronto, execute-o e teste se as operações CRUD funcionam conforme o
esperado.
Web também usa DataContractSerializer para executar a serialização XML. Se você quiser usar o XmlSerializer,
precisará configurar a API da Web de acordo.
Para configurar a API da Web para usar XmlSerializer em vez de DataContractSerializer, abra o
Arquivo WebApiConfig.cs da pasta App_Start. Em seguida, adicione o código mostrado na Listagem 10-25.
config.MapHttpAttributeRoutes();
config.Formatters.XmlFormatter.UseXmlSerializer = verdadeiro;
config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/
{controller}/{id}", defaults: new { id = RouteParameter.Optional }
);
}
303
Machine Translated by Google
Resumo
Este capítulo apresentou a você duas estruturas de desenvolvimento de serviço — Windows Communication
Foundation (WCF) e Web API. O WCF permite que você desenvolva serviços REST baseados em operações e em
recursos. Por outro lado, a API da Web permite que você crie serviços REST baseados em recursos. WCF e Web API
podem lidar com vários formatos de dados, embora JSON e XML sejam bastante populares atualmente. Nosso interesse
era ver como os dados podem ser enviados e recebidos no formato XML usando esses frameworks.
A criação de um serviço WCF baseado em operações envolve três partes: serviço, host e aplicativo cliente.
O aplicativo cliente gera um proxy para o serviço invocar suas operações. WCF usa configuração XML
para configurar o serviço. Além disso, o WCF usa mensagens SOAP para passar os dados por HTTP.
A criação de um serviço REST usando WCF e Web API envolve duas partes: o serviço e o aplicativo
cliente. Ambas as estruturas podem usar o formato XML para transferir os dados pela rede. O aplicativo cliente não cria
nenhum proxy, mas chama o serviço diretamente usando HttpClient. Uma maneira simples de acessar os dados XML
retornados por um serviço REST é carregá-los em XmlDocument.
304
Machine Translated by Google
CAPÍTULO 11
XML no SQLServer
A maioria dos aplicativos de negócios armazena dados em algum tipo de armazenamento de dados, que geralmente é um banco
de dados relacional. Para esse fim, o SQL Server é um dos principais produtos da Microsoft. Como muitos aplicativos dependem de
dados XML, a Microsoft achou necessário incorporar um forte suporte para XML em seu mecanismo de banco de dados.
Como o SQL Server é um banco de dados tão popular, vale a pena aprender seus recursos XML. Além disso, seria
seria interessante ver como esses recursos podem ser consumidos a partir dos aplicativos construídos sobre o .NET Framework.
Neste capítulo, você aprenderá sobre o seguinte:
Você deve observar, no entanto, que um exame extensivo de todos os recursos XML do SQL Server está fora de cogitação.
o escopo deste livro. A intenção aqui é familiarizá-lo com os recursos XML do SQL Server.
ÿ Observação Usei o SQL Server 2012 Developer Edition para desenvolver os exemplos deste capítulo. Muitos dos
exemplos devem funcionar com versões anteriores (como SQL Server 2010) do produto também, mas sugiro que você
instale as versões mais recentes para evitar incompatibilidades. Você também precisará instalar as classes gerenciadas
SQLXML. Você pode baixar o SQLXML do site oficial da Microsoft.
Modo Descrição
AUTO Retorna os resultados da consulta SELECT como fragmentos XML. Por padrão, ele retorna os dados como elementos
XML. O nome do elemento XML é igual ao nome da tabela e os valores da coluna são retornados como atributos
XML. Você tem a opção de retornar todas as colunas como elementos em vez de atributos.
CRU Retorna os resultados como um elemento <row>. Os valores da coluna são retornados como atributos XML.
CAMINHO
Permite definir o aninhamento do XML retornado usando a sintaxe XPath simples.
Para testar esses modos da cláusula FOR XML, você executará algumas consultas SELECT no Northwind
base de dados. Para executar as consultas, você pode usar o SQL Server Management Studio ou o Server Explorer do Visual Studio
(mostrado na Figura 11-1).
O modo AUTOMÁTICO
Abra o SQL Server Management Studio e emita a instrução SELECT mostrada na Listagem 11-1.
306
Machine Translated by Google
A instrução SELECT da Listagem 11-1 seleciona três colunas — EmployeeID, FirstName e LastName — da tabela
Employees. A Listagem 11-1 também mostra os dados retornados no formato XML. Observe como o nome da tabela é usado para
os nomes dos elementos XML (<EMPLOYEES>) e os nomes das colunas são usados para o atributo
nomes.
Você notou algo sobre a capitalização de caracteres do XML retornado? Depende totalmente do nome da tabela e das
colunas usadas na instrução SELECT. Por exemplo, se você especificar nomes de coluna em maiúsculas, os atributos XML
estarão em maiúsculas. Talvez seja necessário ter isso em mente ao analisar os dados XML em seu aplicativo. Por padrão, o
modo AUTO retorna todos os valores de coluna como atributos XML. Se desejar, você pode retorná-los como elementos. Isso é
obtido usando a cláusula ELEMENTS com o modo AUTO. A Listagem 11-2 mostra como a cláusula ELEMENTS funciona.
<FUNCIONÁRIOS>
<EMPLOYEEID>1</EMPLOYEEID>
<FIRSTNAME>Nancy</FIRSTNAME>
<LASTNAME>Davolio</LASTNAME>
</EMPLOYEES>
....
Como você pode ver, especificamos a cláusula ELEMENTS após o modo AUTO. Observe como os valores da coluna são
retornaram como elementos desta vez. Os nomes dos elementos são iguais aos nomes das colunas.
ÿ Observação Os dados XML retornados pela cláusula FOR XML no código anterior não são bem formados por padrão.
Não inclui o elemento raiz. No entanto, como você verá mais tarde, você mesmo pode especificar o elemento raiz.
O modo RAW
O modo RAW da cláusula FOR XML retorna os dados XML como zero ou mais elementos XML. Por padrão, o nome dos
elementos é <linha>. Você pode alterar esse comportamento padrão especificando você mesmo um nome de elemento. Os
valores da coluna são retornados como atributos XML. A Listagem 11-3 mostra o uso do modo RAW.
Como você pode ver, a cláusula FOR XML é seguida pelo modo RAW. O XML retornado contém elementos <row> com
atributos contendo os valores da coluna. Se quiser alterar o nome do elemento padrão, você pode especificar seu próprio nome
de elemento, conforme mostrado na Listagem 11-4.
307
Machine Translated by Google
Como você pode ver, agora especificamos Employee como o nome do elemento entre parênteses. Este nome de elemento
é dado a todas as linhas retornadas.
<xsd:schema targetNamespace="urn:schemas-microsoft-com:sql:SqlRowSet1"
xmlns:schema="urn:schemas-microsoft-com:sql:SqlRowSet1" xmlns:xsd="http://www.w3.org /2001/
XMLSchema" xmlns:sqltypes="http://schemas.microsoft.com/sqlserver/2004/sqltypes"
elementFormDefault="qualified"> <xsd:import namespace="http://schemas.microsoft.com/sqlserver /
2004/sqltypes" schemaLocation="http://schemas.microsoft.com/sqlserver/2004/sqltypes/
sqltypes.xsd"/> <xsd:element name="Employees"> <xsd:complexType> <xsd:attribute name ="EmployeeID"
type="sqltypes:int" use="required"/> <xsd:attribute name="FirstName" use="required"> <xsd:simpleType><xsd:restriction
base="sqltypes:nvarchar" sqltypes :localeId="1033" sqltypes:sqlCompareOptions="IgnoreCase IgnoreKanaType
IgnoreWidth" sqltypes:sqlSortId="52"> <xsd:maxLength value="10"/> </xsd:restriction> </xsd:simpleType> </xsd:
attribute> <xsd:attribute name="LastName" use="required"> <xsd:simpleType> <xsd:restriction base="sqltypes:nvarchar"
sqltypes:localeId="1033" sqltypes: sqlCompareOptions="IgnoreCase IgnoreKanaType IgnoreWidth"
sqltypes:sqlSortId="52"> <xsd:maxLength value="20"/> </xsd:restriction> </xsd:simpleType> </xsd:attribute>
308
Machine Translated by Google
</xsd:complexType> </
xsd:element> </
xsd:schema>
<Employees xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" EmployeeID="1"
FirstName="Nancy" LastName="Davolio"/>
<Employees xmlns="urn:schemas-microsoft-com:sql:SqlRowSet1" EmployeeID="2"
FirstName="Andrew" LastName="Fuller"/>
....
Como você pode ver, a cláusula XMLSCHEMA retorna o esquema XML junto com os dados.
O modo PATH
Embora os modos AUTO e RAW retornem dados no formato XML, você tem muito pouco controle sobre as convenções de
aninhamento e nomenclatura dos dados retornados. O modo PATH, por outro lado, permite que você especifique a estrutura de
aninhamento, bem como os nomes de elementos e atributos usando a sintaxe XPath simples. Suponha que você queira recuperar
registros da tabela Employees no formato mostrado na Listagem 11-6.
<ID do funcionário="1">
<Nome>
<FirstName>Nancy</FirstName>
<LastName>Davolio</LastName>
</Nome>
</Empregado>
Cada registro deve ser retornado como um elemento <Employee>. O valor da coluna EmployeeID deve ser
retornado como o atributo ID do elemento <Employee>. Deve haver um elemento chamado <Name> com dois subelementos:
<FirstName> e <LastName>. Os elementos <FirstName> e <LastName> devem conter
dados das colunas FirstName e LastName, respectivamente. Para recuperar dados XML neste formato, você pode usar o modo
PATH, conforme mostrado na Listagem 11-7.
Como você pode ver, a consulta SELECT agora especifica certas informações extras junto com os nomes das colunas.
Queremos retornar o valor da coluna EmployeeID como o atributo ID e, portanto, a consulta adiciona @ID após a coluna
EmployeeID. Da mesma forma, as colunas FirstName e LastName são seguidas pelo aninhamento desejado e pelos nomes dos
elementos, ou seja, Name/FirstName e Name/LastName, respectivamente. O nome do elemento gerado é especificado após o modo
PATH entre parênteses. Observe como a sintaxe XPath (@, /) é usada para especificar os atributos e o aninhamento de elementos.
O modo EXPLÍCITO
O modo EXPLICIT é possivelmente o modo mais confuso da cláusula FOR XML. Por um lado, aumenta a complexidade
da instrução SELECT, mas, por outro, fornece um controle muito mais refinado sobre a saída resultante.
309
Machine Translated by Google
ÿ Observação Discuto o modo EXPLICIT apenas para dar uma ideia de como ele renderiza a saída XML.
De forma alguma este livro tenta ensinar a você o modo EXPLÍCITO além do básico. Se você quiser aprender sobre
o modo EXPLICIT em detalhes, considere a leitura da documentação oficial do produto.
Suponha que você queira retornar o conteúdo XML conforme mostrado na Listagem 11-8.
<Empregado ID="1">
<FirstName>Nancy</FirstName>
<LastName>Davolio</LastName>
</Empregado>
Você pode identificar dois níveis nesta marcação. O nível 1 consiste no elemento <Employee> e o nível 2 consiste nos
elementos <FirstName> e <LastName>. A coluna EmployeeID é gerada como o atributo EmpID do elemento <Employee> e,
portanto, pertence ao nível 1.
Ao usar o modo EXPLICIT para gerar essa saída XML, precisamos escrever duas consultas SELECT:
•A primeira consulta descreverá a estrutura, o aninhamento e os nomes dos elementos das várias colunas
envolvidas.
•A segunda consulta realmente buscará os dados. Os resultados das duas consultas serão
mesclado com uma cláusula UNION.
SELECIONE
1 AS Tag,
NULL AS Parent,
EmployeeID AS [Employee!1!EmpID], FirstName
AS [Employee!1!FirstName!element], LastName AS [Employee!1!
LastName!element]
DE Funcionários
A consulta seleciona cinco colunas: 1, NULL, EmployeeID, FirstName e LastName. As três últimas colunas
são óbvios, mas o que são 1 e NULL? As colunas Tag e Parent são colunas implícitas na tabela resultante que são criadas
internamente pelo SQL Server:
•A coluna Tag especifica o nível de aninhamento do elemento atual. Um valor de Tag de 1 indica que esta
consulta está definindo a estrutura para o nível 1 da saída XML.
Cada coluna especificada após a coluna Pai tem algumas especificações de metadados dentro de um quadrado
colchetes. Múltiplos pedaços de metadados são separados por um caractere de exclamação (!):
310
Machine Translated by Google
• Se você especificar apenas essas três partes, o valor da coluna será gerado como um atributo. Para
especificar que deve ser gerado como um elemento, você deve especificar a quarta parte. A quarta parte é
uma palavra-chave predefinida chamada elemento.
Em nosso exemplo, o elemento de nível superior é <Employee>. Este nome de elemento de nível superior é decidido pelo
primeira coluna real na lista SELECT (no nosso caso, EmployeeID). O nome do elemento de nível superior é obtido da primeira
informação de metadados especificada após a coluna EmployeeID. Queremos gerar o valor da coluna EmployeeID como um atributo
chamado EmpID. Assim, o nível pai do atributo EmpID é tag 1.
Por fim, a terceira informação de metadados especifica que o nome do atributo é EmpID.
Os metadados para as colunas FirstName e LastName especificam que seu pai é o elemento de nível 1
e eles devem ser gerados como elementos <FirstName> e <LastName>, respectivamente.
A segunda consulta SELECT é mostrada na Listagem 11-10.
SELECIONE
1,
NULO,
ID do Empregado,
Primeiro nome,
Sobrenome
DE Funcionários
ORDENAR POR
[Employee!1!EmpID],
[Employee!1!FirstName!element], [Employee!1!
LastName!element]
PARA XML EXPLÍCITO
A consulta seleciona dados para tag 1 e seleciona as colunas EmployeeID, FirstName e LastName. A cláusula ORDER BY indica a
sequência em que os elementos aparecerão no XML resultante. Finalmente, a consulta adiciona uma cláusula FOR XML EXPLICIT.
Agora que você entende as duas consultas, pode usar a cláusula UNION ALL, conforme mostrado na Listagem 11-11.
SELECT
1 AS Tag,
NULL AS Parent,
EmployeeID AS [Employee!1!EmpID], FirstName
AS [Employee!1!FirstName!element], LastName AS [Employee!1!
LastName!element]
FROM Empregados
UNION Todos
SELECT 1, NULL,
EmployeeID,
FirstName, LastName
FROM Employees
ORDER BY [Employee!
1!EmpID],
311
Machine Translated by Google
[Funcionário!1!Nome!elemento], [Funcionário!
1!Sobrenome!elemento]
PARA XML EXPLÍCITO
A cláusula UNION ALL combina os resultados dessas duas consultas e você obtém a saída XML mostrada na Listagem
11-8. Vamos um pouco mais longe e vamos supor que você deseja recuperar o XML no formato mostrado na Listagem 11-12.
<Funcionário empid="1">
<Nome>
<FName>Nancy</FName>
<LName>Davolio</LName>
</Nome>
</Empregado>
A saída XML tem mais um nível de aninhamento. Os elementos <FName> e <LName> estão incluídos no elemento <Name>,
que por sua vez está incluído no elemento <Employee>. A coluna EmployeeID é gerada como um atributo empid. As consultas
SELECT necessárias para gerar essa saída são fornecidas na Listagem 11-13.
Listagem 11-13. Consultas SELECT para geração de saída, conforme mostrado na Listagem 11-12
SELECIONE
1 AS Tag,
NULL AS Parent,
EmployeeID AS [employee!1!empid], FirstName
AS [Name!2!FName!element], LastName AS [Name!
2!LName!element]
DE Funcionários
UNIÃO TODOS
SELECT 2 AS Tag, 1
AS Parent, EmployeeID,
FirstName, LastName
FROM Employees
ORDER BY [Employee!
1!empid], [Name!2!
FName!element],
[Name!2!LName!element]
A primeira instrução SELECT define a estrutura da saída XML resultante. Observe que, desta vez, as colunas FirstName e
LastName definem seu elemento pai como <Name> e têm um nível de tag de 2. Elas também definem nomes de elemento para as
colunas FirstName e LastName como <FName> e <LName>, respectivamente. A segunda consulta define a tag 2. Ela especifica que
o pai da tag 2 é a tag 1 por meio da coluna Parent. Ele ordena o conjunto de resultados usando a cláusula ORDER BY como antes.
Se você executar esse script no Management Studio, deverá ver a saída XML mostrada na Listagem 11-12.
312
Machine Translated by Google
<MyRoot>
<Employees EmployeeID="1" FirstName="Nancy" LastName="Davolio"/>
<Employees EmployeeID="2" FirstName="Andrew" LastName="Fuller"/>
....
</MyRoot>
Como você pode ver, a cláusula ROOT é anexada ao final da consulta com o nome do elemento raiz em
parênteses. O XML retornado agora está agrupado dentro desse elemento raiz.
Usando OPENXML
Como você viu, a cláusula FOR XML do SQL Server permite que você recupere dados relacionais no formato XML.
No entanto, há outra maneira de fazer isso — a função OPENXML, que permite ler dados XML de maneira relacional.
Suponha que você tenha marcação XML que contém uma lista de funcionários e seu objetivo é importar essa lista para a tabela
Funcionários. Na ausência de algo como OPENXML, realizar essa tarefa seria tedioso. Como você verá em breve, a função
OPENXML torna seu trabalho muito mais fácil. A Listagem 11-15 mostra a marcação XML de origem contendo a listagem de
funcionários.
<Funcionários>
<Employee EmployeeID="10" FirstName="John" LastName="Moore" />
<Employee EmployeeID="11" FirstName="Bill" LastName="Short" />
</Empregados>
Como você pode ver, o elemento raiz da marcação é <Employees>. Além disso, ele contém elementos <Employee>
que representam registros de funcionários. O EmployeeID, FirstName e LastName aparecem como atributos do elemento
<Employee>. Para ler qualquer marcação XML usando a função OPENXML, você precisa executar as seguintes etapas:
313
Machine Translated by Google
SET @xml=
'<Funcionários>
<Employee EmployeeID="10" FirstName="John" LastName="Gates" />
<Employee EmployeeID="11" FirstName="Bill" LastName="Short" />
</Empregados>'
SELECIONE DE
OPENXML(@hDoc,'Funcionários/ Funcionário',0)
WITH (EmployeeID int,FirstName varchar(50),LastName varchar(50)) )
O script na Listagem 11-16 declara duas variáveis denominadas hDoc e xml. A variável inteira hDoc é usada
posteriormente para armazenar um identificador para o documento XML carregado. A variável VARCHAR xml é usada para
armazenar a marcação XML mostrada na Listagem 11-15 como uma string. A instrução SET atribui a marcação XML à variável xml.
Em seguida, chamamos o procedimento armazenado do sistema sp_xml:preparedocument, que analisa e carrega a marcação
XML fornecida na memória. Ele retorna um identificador para o documento carregado na forma de um inteiro.
Em seguida, esse identificador é coletado na variável hDoc que declaramos anteriormente. Em seguida, uma instrução
INSERT é executada, fazendo uso da função OPENXML. Observe a chamada para OPENXML cuidadosamente. A função
OPENXML é usada em uma instrução SELECT como se fosse uma tabela. Aceita três parâmetros:
•O segundo parâmetro é um padrão XPath apontando para o nó dos dados XML que devem ser tratados
como uma linha. Em nosso exemplo, esse caminho base é Employees/Employee.
•O terceiro parâmetro é um sinalizador que indica o mapeamento entre os dados XML e o conjunto de
linhas relacional. O terceiro parâmetro pode assumir os valores mostrados na Tabela 11-2.
314
Machine Translated by Google
0 Especifica que os atributos dos elementos XML estão fornecendo valores de coluna para o conjunto de linhas
relacional. Este é o padrão.
1 Especifica que os atributos dos elementos XML estão fornecendo valores de coluna para o conjunto de linhas
relacional. Quando combinados com um valor de sinalizador de 2, os atributos são selecionados como valores de
coluna e, em seguida, os valores de elemento são atribuídos às colunas restantes.
2 Especifica que os elementos do XML de origem estão fornecendo valores de coluna para o relacional
conjunto de linhas.
8 Este sinalizador pode ser combinado com 1 ou 2 e indica que os dados consumidos não devem ser copiados
para a propriedade de estouro @mp:xmltext.
Além disso, a cláusula WITH de OPENXML especifica a estrutura do conjunto de linhas resultante. A estrutura pode
ser especificado como uma lista separada por vírgulas de nomes de colunas e seus tipos de dados. Em nosso exemplo, temos
três colunas: EmployeeID, FirstName e LastName. Observe que esses nomes de coluna são iguais aos nomes de atributo na
marcação XML de origem.
Assim, o conjunto de linhas retornado da instrução SELECT e OPENXML é alimentado na instrução INSERT. O
A instrução INSERT adiciona os dados à tabela Employees. Em nosso exemplo, ele adicionará duas linhas.
Após a conclusão da operação INSERT, o documento XML é removido da memória usando outro procedimento armazenado
do sistema: sp_xml:removedocument. Isso aceita o identificador de um documento XML carregado anteriormente usando
sp_xml:preparedocument e limpa a memória consumida pelo documento.
Chamar sp_xml:removedocument é muito importante porque deixar de fazer isso pode desperdiçar memória valiosa do seu aplicativo.
ÿ Nota Uma discussão completa da cláusula OPENXML está fora do escopo deste livro. O objetivo aqui é fornecer uma
ÿ Observação Certifique-se de baixar e instalar o SQLXML 4.0 SP1 antes de continuar com os exemplos restantes deste
capítulo.
315
Machine Translated by Google
SqlXmlAdapterGenericName Usado para interagir com a classe ADO.NET DataSet. Essa classe é análoga à classe
ADO.NET SqlDataAdapter.
Todas as classes anteriores podem usar o provedor SQL Server OLEDB (SQLOLEDB) ou o SQL Native Client
para se comunicar com o banco de dados subjacente. Nas próximas seções, você aprenderá como as classes SQLXML podem
ser usadas em seus aplicativos .NET.
O aplicativo consiste em uma caixa de texto para inserir consultas SELECT. Observe que essas consultas SELECT devem
use algum modo da cláusula FOR XML que você aprendeu anteriormente. O botão Executar executa a consulta e exibe
os resultados em um controle de navegador da Web. O manipulador de eventos Click do botão Execute é mostrado na
Listagem 11-17.
316
Machine Translated by Google
string strConn =
@"Provider=SQLOLEDB;servidor=.\sqlexpress;database=northwind;integrated ÿ
security=SSPI"; SqlXmlCommand cmd = new SqlXmlCommand(strConn); cmd.CommandText =
textBox1.Text; Stream stream= cmd.ExecuteStream(); Leitor StreamReader=novo
StreamReader(fluxo); Escritor StreamWriter =
File.CreateText($"{Application.StartupPath}\\sqlxmlresults.xml");
escritor.Write(leitor.ReadToEnd());
escritor.Close();
webBrowser1.Navigate($"{Application.StartupPath}\\sqlxmlresults.xml"); }
ÿ Observação Certifique-se de alterar a string de conexão do banco de dados para corresponder ao seu ambiente de desenvolvimento
O código declara uma variável de string para armazenar a string de conexão do banco de dados. Observe
o parâmetro Provider da string de conexão, que especifica o provedor SQLOLEDB. Em seguida, o código cria
uma instância da classe SqlXmlCommand passando a string de conexão em seu construtor. A propriedade
CommandText de SqlXmlCommand é definida como a consulta SELECT inserida na caixa de texto e a consulta é
executada chamando o método ExecuteStream() da classe SqlXmlCommand.
O método ExecuteStream() executa sua consulta e retorna um objeto Stream contendo os resultados XML.
Este Stream pode então ser usado ainda mais para ler os dados. No código anterior, o Stream é alimentado para
uma classe StreamReader. Poderíamos ter lido o Stream byte por byte, mas a classe StreamReader facilita nosso
trabalho.
O método CreateText() da classe File cria um novo arquivo XML no local especificado e retorna
um StreamWriter apontando para ele. O XML retornado do banco de dados é lido usando o método
ReadToEnd() da classe StreamReader e, em seguida, gravado no arquivo XML. Finalmente, o método Navigate() do
controle Web Browser é chamado para mostrar ao usuário o arquivo XML.
Existe uma maneira alternativa de fazer a mesma tarefa. Dê uma olhada na Listagem 11-18.
@"Provider=SQLOLEDB;servidor=.\sqlexpress;database=northwind;integrado ÿ
security=SSPI";
SqlXmlCommand cmd = new SqlXmlCommand(strConn);
cmd.CommandText = textBox1.Text; StreamWriter writer =
File.CreateText($"{Application.StartupPath}\\sqlxmlresults.xml");
cmd.ExecuteToStream(writer.BaseStream); escritor.Close();
webBrowser1.Navigate($"{Application.StartupPath}\\sqlxmlresults.xml"); }
317
Machine Translated by Google
O código na Listagem 11-18 é muito semelhante ao da Listagem 11-17. A diferença é que ele chama o método
ExecuteToStream() em vez de ExecuteStream() e, ao fazer isso, emite a saída XML para um Stream existente. A
propriedade BaseStream da classe StreamWriter retorna o Stream subjacente, que é então fornecido ao método
ExecuteToStream().
ÿ Observação Você também pode usar o método ExecuteXmlReader() da classe SqlXmlCommand . Esse
método é idêntico ao método ExecuteXmlReader() da classe SqlCommand que você aprendeu no Capítulo 7.
O aplicativo permite que você busque detalhes de apenas um funcionário cujo EmployeeID esteja especificado na
caixa de texto. Os dados XML retornados são exibidos no controle Web Browser como antes. A Listagem 11-19 mostra
o manipulador de eventos Click do botão Execute.
@"Provider=SQLOLEDB;servidor=.\sqlexpress;database=northwind;integrated ÿ security=SSPI";
318
Machine Translated by Google
File.CreateText($"{Application.StartupPath}\\sqlxmlresults.xml");
cmd.ExecuteToStream(writer.BaseStream); escritor.Close();
webBrowser1.Navigate($"{Application.StartupPath}\\sqlxmlresults.xml"); }
Examine a consulta SELECT cuidadosamente. Possui uma cláusula WHERE com um parâmetro
marcado com um ponto de interrogação (?). Além disso, o método CreateParameter() é chamado na classe
SqlXmlCommand. O método CreateParameter() cria e retorna um novo SqlXmlParameter. Você pode definir a
propriedade Value dessa classe SqlXmlParameter. Se sua consulta tiver mais de um parâmetro, você precisará
chamar o método CreateParameter() uma vez para cada parâmetro. Observe que a sequência de parâmetros na
consulta e a sequência na qual você cria objetos SqlXmlParameter devem ser iguais. Depois de criar o parâmetro
necessário, a saída XML é salva em um FileStream usando o método ExecuteToStream() de SqlXmlCommand.
Preenchendo um DataSet
Um DataSet é um dos objetos mais comumente usados para vinculação de dados e processamento desconectado. É
óbvio que o modelo de objeto SQLXML deve fornecer algum mecanismo para preencher objetos DataSet, e o SqlXmlAdapter
se encaixa no projeto. Ele permite que você preencha um DataSet e reflita as alterações feitas no DataSet no banco de
dados. Para ilustrar o uso de SqlXmlAdapter no preenchimento de um DataSet, você precisa criar um aplicativo como o
mostrado na Figura 11-4.
O aplicativo consiste em um controle DataGridView. Quando o formulário é carregado, um DataSet é preenchido com
todos os registros da tabela Employees e o DataSet resultante é vinculado ao controle DataGridView. O manipulador de
eventos Load que faz esse trabalho é mostrado na Listagem 11-20.
319
Machine Translated by Google
string strConn =
@"Provider=SQLOLEDB;servidor=.\sqlexpress;database=northwind;integrated ÿ security=SSPI";
string sql = "SELECT employeeid,firstname,lastname FROM Employees FOR XML AUTO"; SqlXmlCommand
cmd = new SqlXmlCommand(strConn); cmd.CommandText = sql; DataSet ds = new DataSet(); SqlXmlAdapter
da = new SqlXmlAdapter(cmd); da.Fill(ds); dataGridView1.DataSource = ds.Tables[0].DefaultView; }
O código cria um objeto SqlXmlCommand como antes. Em seguida, ele cria uma nova instância do DataSet e
Classes SqlXmlAdapter. O SqlXmlAdapter aceita o objeto SqlXmlCommand como parâmetro e, portanto, a consulta
SELECT (ou procedimento armazenado) é passado para ele. O método Fill() de SqlXmlAdapter é então chamado passando
um objeto DataSet como parâmetro. O método Fill() preenche o DataSet com os resultados retornados da consulta. Por fim, o
DataSet é vinculado ao controle DataGridView.
simplesmente preenchemos um DataSet com a ajuda da classe SqlXmlAdapter. E se você fizer alterações nos dados do DataSet
e quiser salvar essas alterações no banco de dados? O SqlXmlAdapter fornece o método Update() que atualiza o banco de
dados com quaisquer alterações em seu DataSet. No entanto, você precisa fazer um pouco mais de trabalho do que isso. Ao
preencher o DataSet, você precisa especificar o XSD Schema para o DataTable que está sendo criado. Esse esquema fornece
mapeamento entre os nomes de coluna DataTable e os nomes de coluna da tabela real. Em nosso exemplo, recuperamos três
colunas da tabela Employee: EmployeeID, FirstName e LastName. O esquema para esta estrutura de dados é mostrado na
Listagem 11-21.
O esquema define um elemento raiz chamado <Employees>, que possui três elementos filhos: <EmployeeID>, <FirstName>
e <LastName>. Observe que o esquema define as colunas como elementos e não como atributos.
Para ver a classe SqlXmlAdapter em ação, você precisa desenvolver um aplicativo conforme mostrado na Figura 11-5.
320
Machine Translated by Google
O aplicativo consiste em um controle DataGridView que exibe todos os funcionários da tabela Employees.
Você pode alterar os dados no DataGridView e clicar no botão Atualizar para salvar as alterações no banco de
dados. O código completo que faz esse aplicativo funcionar é mostrado na Listagem 11-22.
321
Machine Translated by Google
O código na Listagem 11-22 mostra várias coisas interessantes. As variáveis SqlXmlCommand, DataSet e
SqlXmlAdapter são declaradas no nível do formulário porque as usaremos em mais de um local. Observe o código
marcado em negrito. Ele define a propriedade RootTag da propriedade SqlXmlCommand. O modo AUTO da cláusula FOR
XML não retorna dados junto com um elemento raiz por padrão, então esta propriedade é usada para indicar o nome do
elemento raiz dentro do qual o resto dos dados XML serão agrupados.
A propriedade CommandType é definida como XPath, indicando que a propriedade CommandText é uma
expressão XPath. Isso significa que, desta vez, a propriedade CommandText não é uma consulta SELECT, mas a
expressão XPath Employees, que retornará vários elementos <Employees>.
A propriedade CommandType da classe SqlXmlCommand é do tipo SqlXmlCommandType. Os valores possíveis
da enumeração SqlXmlCommandType estão listados na Tabela 11-4.
Valor Descrição
DiffGram Indica que CommandText é um DiffGram
Além disso, a propriedade SchemaPath especifica o caminho do arquivo de esquema que criamos anteriormente. Em
seguida, o SqlXmlAdapter preenche um DataSet, que está vinculado ao DataGridView.
Depois que os dados são exibidos no DataGridView, você pode modificá-los. Depois de concluídas as modificações,
você precisa clicar no botão Atualizar. O evento Click do botão Update chama o método Update() do SqlXmlAdapter, que
aceita o DataSet cujas alterações devem ser refletidas no banco de dados. No Capítulo 7, você aprendeu que a classe
DataSet rastreia internamente as alterações feitas nos dados usando o formato DiffGram. O mesmo DiffGram é usado pela
classe SqlXmlAdapter para propagar as alterações de volta ao banco de dados.
322
Machine Translated by Google
O aplicativo consiste em um controle de navegador da Web. Quando o formulário é carregado, uma consulta SELECT é
executada usando SqlXmlCommand. Uma folha de estilo XSLT é então aplicada aos dados XML retornados para transformá-los em
HTML. O documento HTML resultante é então exibido no controle Web Browser.
Antes de escrever qualquer código, você deve criar uma folha de estilo XSLT chamada Employees.xslt, conforme mostrado em
Listagem 11-23.
<tr>
<td>
<xsl:value-of select="@EmployeeID"/> </td> <td>
<xsl:value-of select="@FirstName"/> </td>
323
Machine Translated by Google
<td>
<xsl:value-of select="@LastName"/> </td> </
tr> </xsl:for-each> </table> </body> </html> </
xsl:template > </xsl:folha de estilo>
A folha de estilo itera por todos os elementos <Employees> e renderiza uma tabela HTML. A tabela HTML exibe os
valores de atributo em várias células. Observe que usaremos o modo AUTO da cláusula FOR XML, que retorna valores de
coluna como atributos XML. É por isso que a folha de estilo usa nomes de atributos (@ EmployeeID, @FirstName e
@LastName). O código que realmente executa a consulta SELECT e realiza a transformação é mostrado na Listagem 11-24.
@"Provider=SQLOLEDB;servidor=.\sqlexpress;database=northwind;integrado ÿ
security=SSPI";
SqlXmlCommand cmd = new SqlXmlCommand(strConn);
cmd.CommandText = "SELECT EmployeeID,FirstName,LastName
FROM Employees FOR XML AUTO";
cmd.RootTag = "root"; cmd.XslPath = $"{Application.StartupPath }\
\employees.xslt"; Gravador StreamWriter =
File.CreateText($"{Application.StartupPath}\\sqlxmlresults.htm");
cmd.ExecuteToStream(writer.BaseStream); escritor.Close();
webBrowser1.Navigate($"{Application.StartupPath}\\sqlxmlresults.htm"); }
Observe o código marcado em negrito. Desta vez, a instrução SELECT não contém uma cláusula ROOT. Nós
poderia realmente tê-lo usado, mas o código consegue a mesma coisa com a ajuda da propriedade RootTag da classe
SqlXmlCommand. Lembre-se de que, na ausência de uma cláusula ROOT na consulta FOR XML, os dados XML retornados
não contêm um elemento raiz. A propriedade RootTag de SqlXmlCommand especifica o nome da marca raiz dentro da qual a
saída da consulta SELECT será agrupada.
A folha de estilo XSLT a ser usada para transformação é especificada por meio da propriedade XslPath da
classe SqlXmlCommand. Dessa forma, a classe SqlXmlCommand sabe qual folha de estilo aplicar aos dados XML
retornados. O restante do código deve ser familiar para você, conforme discutimos nos exemplos anteriores. Ele simplesmente
salva os dados XML transformados em um arquivo de disco e exibe esse arquivo no controle do navegador da Web.
324
Machine Translated by Google
O elemento raiz <ROOT> é um elemento definido pelo usuário, mas o namespace urn:schemas-microsoft com:xml-
sql é necessário. O elemento <ROOT> contém uma seção opcional chamada <sql:header>, que é usada para definir
parâmetros usados por sua consulta (se houver). Cada parâmetro é especificado usando um elemento <sql:param>. O atributo
name do elemento <sql:param> indica o nome do parâmetro, enquanto o valor do parâmetro é armazenado nas tags
<sql:param> e </sql:param>. A consulta real é armazenada na seção <sql:query>. A consulta usa o parâmetro prefixando seu
nome com o símbolo @.
Para usar esse arquivo de modelo XML, você precisa criar um aplicativo como o mostrado na Figura 11-7.
325
Machine Translated by Google
string strConn =
@"Provider=SQLOLEDB;servidor=.\sqlexpress;database=northwind;integrated ÿ security=SSPI";
SqlXmlCommand cmd = new SqlXmlCommand(strConn); cmd.CommandType =
SqlXmlCommandType.TemplateFile; cmd.CommandText = $"{Application.StartupPath}\
\querytemplate.xml"; DataSet ds = new DataSet(); SqlXmlAdapter da = new SqlXmlAdapter(cmd);
da.Fill(ds); dataGridView1.DataSource = ds.Tables[0].DefaultView; }
Observe o código marcado em negrito. Desta vez, a propriedade CommandType da classe SqlXmlCommand é definida
como TemplateFile. Isso indica que a propriedade CommandText especificará o caminho do arquivo de modelo XML. Em
seguida, a propriedade CommandText é definida como o caminho do arquivo de modelo XML que acabamos de criar na Listagem 11-25.
A instância de SqlXmlAdapter é usada como antes para preencher um DataSet. O DataSet é finalmente vinculado ao controle
DataGridView.
O aplicativo consiste em um controle DataGridView para exibir todos os registros da tabela Employees. Existem dois botões: Salvar
DiffGram e Atualizar DiffGram. O primeiro salva o conteúdo do DataSet em um arquivo de disco no formato DiffGram. O último botão lê o
DiffGram salvo anteriormente e atualiza o banco de dados.
O código deve ser familiar para você, pois você o usou nos exemplos anteriores. Ele simplesmente preenche um DataSet
usando a classe SqlXmlAdapter. O DataSet atua como o DataSource para o controle DataGridView. O código que salva este DataSet como
um DiffGram vai no evento Click do botão Save DiffGram e é mostrado na Listagem 11-28.
O código chama o método WriteXml() do DataSet para salvar seu conteúdo em um arquivo XML (Employees.xml).
O parâmetro XmlWriteMode de WriteXml() indica que o formato DiffGram deve ser usado durante a gravação dos dados. Este DiffGram é
executado no banco de dados quando você clica no botão Atualizar DiffGram. O manipulador de eventos Click do botão Update DiffGram é
mostrado na Listagem 11-29.
327
Machine Translated by Google
cmd.ExecuteNonQuery();
MessageBox.Show("DiffGram atualizado para o banco de dados com sucesso!"); }
O código abre o arquivo Employees.xml em um objeto StreamReader. Em seguida, ele cria uma instância da classe
SqlXmlCommand e define a propriedade CommandType da instância SqlXmlCommand como DiffGram. É assim que você informa ao
SqlXmlCommand sobre sua intenção de atualizar um DiffGram. Quando o CommandType é DiffGram, a propriedade CommandText deve
conter o próprio DiffGram. O método ReadToEnd() de StreamReader lê o DiffGram completo e o atribui à propriedade CommandText.
Se você deseja atualizar o banco de dados usando o método DiffGram, deve especificar o SchemaPath
propriedade também. Nesse caso, o esquema é o mesmo que criamos na Listagem 11-21 anteriormente. Finalmente, o método
ExecuteNonQuery() do SqlXmlCommand é chamado para salvar todas as alterações no banco de dados. O método ExecuteNonQuery()
é usado para executar consultas que não retornam nada. Em nosso exemplo, simplesmente queríamos atualizar o DiffGram no banco de
dados e, portanto, usamos o método ExecuteNonQuery().
ÿ Observação Assim como DiffGram, o objeto SqlXmlCommand também permite atualizar UpdateGrams. O formato
UpdateGram é semelhante ao DiffGram, pois mantém as versões diferenciais dos dados. No entanto, a classe DataSet não
possui nenhum método para serializar a si mesma no formato UpdateGram. Você pode pensar no DiffGram como um subconjunto
do UpdateGram.
ÿ Observação Um documento XML é uma marcação que contém o elemento raiz, enquanto um fragmento XML é uma
marcação sem nenhum elemento raiz. Lembre-se que a cláusula FOR XML por padrão retorna fragmentos XML e não
documentos.
Além de armazenar dados XML, você também pode executar operações XQuery e instruções especiais de manipulação
de dados XML nos dados. Os dados XML podem ter um Esquema XSD anexado a eles para que as validações de dados possam ser
executadas. Você também pode indexar tabelas com base em uma coluna XML.
ÿ Observação XQuery é uma recomendação do W3C que lida com a consulta de documentos XML. Você pode pensar em XQuery
como SQL para dados XML. A sintaxe XQuery é baseada na sintaxe de expressão XPath.
Para começar, vamos ver como adicionar uma coluna do tipo XML a uma tabela do SQL Server.
328
Machine Translated by Google
A tabela XMLDocs consiste em duas colunas: Id e XmlData. A primeira coluna é a primária e está marcada como uma coluna de
identidade, e a última é do tipo XML.
dados XML é semelhante a qualquer outro tipo de dados. No entanto, há alguns pontos a serem lembrados. A Listagem 11-30 mostra como
usar as instruções INSERT e UPDATE em uma coluna do tipo XML.
Como você pode ver, para um INSERT ou um UPDATE em uma coluna do tipo de dados XML, você pode usar dados XML no formato
de string. Você também pode declarar uma variável do tipo XML em seus scripts Transact-SQL (T-SQL), conforme mostrado na Listagem
11-31.
329
Machine Translated by Google
<LastName> Davolio</LastName>
</Empregado>'
ATUALIZAR xmldocs
SET xmldata=@xmldata
ONDE Id=1
O script declara uma variável chamada xmldata do tipo XML e armazena algumas marcações XML nela. A variável xmldata
é então usada na instrução UPDATE. Se você deseja converter explicitamente um valor de string no tipo de dados XML, pode usar a função
CONVERT conforme mostrado na Listagem 11-32.
ATUALIZAR xmldocs
SET xmldata=CONVERT(xml,@xmldata,0)
ONDE Id=1
O primeiro parâmetro para a função CONVERT é o tipo de dados de destino. O segundo parâmetro são os dados de
origem a serem convertidos e o terceiro parâmetro é o estilo. O valor 0 indica que espaços em branco insignificantes serão descartados.
Você deve estar se perguntando: se os dados XML podem ser representados como uma string, por que desejaríamos usar variáveis
XML? A resposta é, usar o tipo de dados XML é recomendado porque o tipo de dados XML verifica se os dados XML estão bem formados.
Método Descrição
consulta() Consulta uma coluna ou variável XML com base em alguma expressão XQuery e retorna os resultados da
consulta.
valor() Consulta uma coluna ou variável XML e retorna um valor escalar do tipo de dados SQL.
modificar() Modifica o conteúdo de uma coluna ou variável de tipo de dados XML com a ajuda da linguagem de modificação
de dados XML, discutida posteriormente neste capítulo.
330
Machine Translated by Google
query() é usado para consultar dados XML usando expressões XQuery. A Listagem 11-33 ilustra como esse método é
usado.
<Employee EmployeeID="2">
<FirstName>Nancy</FirstName>
<LastName> Davolio</LastName>
</Empregado>
A consulta SELECT usa o método query() na coluna XmlData. O método query() aceita uma expressão XQuery
válida e retorna os nós correspondentes. Em nosso exemplo, buscamos o elemento <Employee> cujo valor do atributo
EmployeeID é 1. Como você pode ver, a sintaxe XQuery é baseada na sintaxe XPath.
value() aceita uma expressão XQuery e retorna um valor escalar (único). A Listagem 11-34 mostra o uso desse método.
-----------
1
Neste exemplo, estamos tentando selecionar o valor do atributo EmployeeID do primeiro funcionário. Observe que
a expressão /Employee/@EmployeeID retorna várias linhas e, portanto, especificamos o índice de linha a ser acessado
usando a notação de matriz. O valor escalar EmployeeID deve ser representado como um número inteiro e, portanto, o
segundo parâmetro é int. Observe que o nome do tipo de dados deve ser colocado entre aspas.
O método exist() retorna 1 se a expressão XQuery retornar pelo menos um nó, 0 se a expressão XQuery
A expressão retorna zero nós e NULL se a coluna XML for nula.
331
Machine Translated by Google
UPDATE xmldocs
SET xmldata.modify ('
substitua o valor de (/
Employee/@EmployeeID)
[1] por "10"')
UPDATE xmldocs
SET xmldata.modify ('
delete (/
Employee[@EmployeeID=3]) ')
A primeira consulta UPDATE usa o método modify() da coluna XML para especificar um XML DML de inserção
instrução que insere um novo nó <Employee> no final da marcação existente. Observe esta sintaxe
cuidadosamente. Toda a instrução insert é colocada entre aspas e atua como um parâmetro para o método
modify(). A primeira expressão na instrução insert é a nova marcação XML a ser inserida seguida pela cláusula after.
Em nosso exemplo, queremos inserir o novo <Employee> após o segundo nó <Employee> e, portanto, especificamos
(/Employee)[2]. Você também pode usar as cláusulas before, as first e as last.
A segunda consulta UPDATE usa o método modify() junto com o valor de substituição de XML DML
declaração. O valor de substituição da instrução aceita duas expressões:
Por fim, a terceira instrução UPDATE usa o método modify() junto com a instrução delete XML DML. A
instrução delete usa a expressão com base na qual a marcação deve ser excluída. Em nosso exemplo, queremos
excluir um funcionário com EmployeeID igual a 3.
332
Machine Translated by Google
Resumo
O SQL Server oferece forte integração com XML. Este capítulo apresentou muitos dos recursos XML do SQL Server, que
permitem exibir dados relacionais como XML. Isso é feito com a ajuda da cláusula FOR XML da instrução SELECT. Você
também pode examinar os dados XML de maneira relacional usando a função OPENXML.
Você também viu como o SQL Server facilita a recuperação de dados em um aplicativo cliente com a ajuda das
classes gerenciadas SQLXML. Essas classes permitem que você selecione dados como um fluxo, um XmlReader ou um DataSet.
Eles também permitem que você atualize UpdateGrams e DiffGrams no banco de dados. O tipo de dados XML do SQL Server
pode ser usado para armazenar documentos XML inteiros ou fragmentos e permite manipulá-los via XML DML.
333
Machine Translated by Google
CAPÍTULO 12
Até agora, você aprendeu a trabalhar com seus próprios dados XML. Isso inclui leitura, gravação, validação, serialização e consulta
de dados XML. A Microsoft tem usado XML extensivamente no .NET Framework.
Esse uso de XML vem em diferentes tipos, como marcação XAML de nota, marcação de controle de servidor de ASP.NET e sistema de
configuração do .NET Framework. Entender o uso de XML no .NET Framework é, portanto, essencial para qualquer desenvolvedor .NET.
Este capítulo apresenta muitos desses recursos.
Especificamente, você aprende sobre os seguintes tópicos:
• Compreender os controles do servidor ASP.NET, como XML, fonte de dados XML, TreeView, Menu e
SiteMap
Observe que, embora este capítulo abranja uma ampla gama de tópicos, de forma alguma ele fornece uma descrição exaustiva
tratamento a esses temas. O foco aqui é apontar brevemente o uso de XML em várias áreas do .NET Framework.
A marcação XAML é uma marcação XML e, portanto, segue todas as regras da gramática XML. Você pode criar/
edite uma marcação XAML em qualquer editor XML/texto, mas o Visual Studio fornece um editor avançado para trabalhar com ela.
Apenas para entender a aparência da marcação XAML, você criará um aplicativo WPF conforme mostrado na Figura 12-1.
Este aplicativo deve parecer familiar para você porque você desenvolveu um aplicativo Windows Forms
semelhante no Capítulo 2. No que diz respeito à funcionalidade do aplicativo, é exatamente o mesmo — adicionar, atualizar
e excluir funcionários do arquivo Employees.xml. Não vamos discutir a funcionalidade novamente para economizar espaço.
Nosso principal interesse aqui é a marcação XAML. Você pode obter o código-fonte completo deste aplicativo no download do
código-fonte deste livro.
Para desenvolver esse aplicativo, crie um novo projeto de aplicativo WPF usando o Visual Studio (consulte a Figura 12-2).
336
Machine Translated by Google
Assim que o projeto for criado, você notará dois arquivos .xaml nele chamados App.xaml e MainWindow.xaml.
Nosso interesse é o arquivo MainWindow.xaml, pois ele define a interface do usuário de nosso aplicativo. Você pode adicionar janelas WPF
usando a caixa de diálogo Adicionar novo item.
Você pode abrir o arquivo MainWindow.xaml no Visual Studio Designer (veja a Figura 12-3) e editá-lo de duas maneiras:
•Arraste e solte os controles WPF da caixa de ferramentas na superfície de design e defina suas propriedades. Fazer
isso irá gerar certa marcação XAML para você.
O arquivo MainWindow.xaml pronto para edição é mostrado na Figura 12-3. A Figura 12-4 mostra a caixa de ferramentas com
Controles do WPF.
337
Machine Translated by Google
Você pode tentar brincar com o designer apenas para obter alguma familiaridade. E tente projetar uma interface de
usuário do aplicativo conforme mostrado anteriormente. Não entraremos em detalhes sobre o processo de design da IU
aqui. A marcação XAML responsável pela interface do usuário é mostrada na Listagem 12-1 (para economizar espaço,
parte da marcação foi omitida da listagem).
<Window x:Class="WpfAndXaml.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://
schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http:/ /schemas.microsoft.com/
expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-
compatibility/2006" xmlns:local="clr-namespace:WpfAndXaml" mc:Ignorable=" d"
338
Machine Translated by Google
Embora não vamos entender a marcação do ponto de vista do WPF, vamos inspecioná-la rapidamente do ponto
de vista do XML.
A marcação tem o elemento raiz Window indicando que é uma janela. Existem certos namespaces usados pelos
aplicativos WPF. Vale a pena conhecer dois deles porque você os encontrará em todos os documentos WPF XAML:
O atributo x:Class especifica que uma classe C# chamada MainWindow conterá o código, como manipuladores
de eventos. A classe MainWindow herda da classe Window (indicada pelo elemento <Window>). Essa classe geralmente
é chamada de classe code-behind.
A seção <Grid> atua como um esquema de layout para o contêiner dos controles WPF alojados na janela.
Dentro, existem elementos de marcação como <ComboBox>, <Label>, <TextBox> e <Button> que representam os
respectivos elementos da interface do usuário. Cada um dos elementos tem seu próprio conjunto de atributos (como
Content, Text, HorizontalAlignment, VerticalAlignment e Margin).
Na verdade, esses elementos são mapeados para determinadas classes definidas pela estrutura do WPF e
geralmente são chamados de tipos de apoio. Por exemplo, o elemento <Button> é mapeado para a classe
System.Windows.Controls.Button. Os atributos Margin e FontSize do elemento <Button> correspondem às propriedades
Margin e FontSize da classe Button, respectivamente.
Até agora tudo bem. Agora, vamos ver como adicionar manipuladores de eventos aos botões. Clique duas vezes em
qualquer um dos controles Button para abrir a classe MainWindow (o arquivo MainWindow.xaml.cs) e o manipulador de eventos
Click desse botão. O manipulador de eventos Click do botão1 (Adicionar) é mostrado na Listagem 12-2.
339
Machine Translated by Google
public MainWindow() {
InitializeComponent();
}
O método button1_Click() representa o manipulador de eventos Click do botão. Depois de gerar esse manipulador de eventos
vazio, examine a marcação XAML novamente.
<Botão x:Nome="botão1"
Conteúdo="Adicionar"
HorizontalAlignment="Esquerda"
VerticalAlignment="Topo"
Largura="75"
Margem="49,262,0,0"
Click="button1_Click"
Altura="24"/>
Como você pode ver, o atributo Click foi adicionado ao elemento <Button> que aponta para o Click
manipulador de eventos.
Como mencionado, não discutiremos o código responsável pelo funcionamento deste aplicativo, pois ele é bastante semelhante
ao exemplo que você desenvolveu no Capítulo 2. Você pode obter o código-fonte completo no download do código-fonte do livro.
Você pode até copiar e colar o código do manipulador de eventos em seu projeto para fazer o aplicativo funcionar conforme o esperado.
Quando você compila o aplicativo, a marcação XAML é convertida em um tipo CLR e mesclada com a classe code-behind
(observe que a classe code-behind é uma classe parcial) para formar uma única unidade de código. Agora você pode executar o
aplicativo e ver se funciona conforme o esperado.
340
Machine Translated by Google
A interface do usuário do aplicativo consiste em um DataGrid que exibe dados XML do arquivo Employees.xml. O DataGrid
tem cinco colunas que exibem EmployeeID, FirstName, LastName, HomePhone e Notes.
Para desenvolver este aplicativo, crie um novo projeto WPF como antes e arraste e solte um controle Grid nele.
Em seguida, configure um recurso para a grade (não confundir com DataGrid), conforme mostrado na Listagem 12-3.
<Grid.Resources>
<XmlDataProvider x:Key="EmployeeData"
Origem="Empregados.xml"
XPath="/employees/employee"/> </
Grid.Resources>
Um recurso é um objeto que você deseja usar frequentemente em um aplicativo. Aqui você configura
XmlDataProvider como um recurso. Um objeto XmlDataProvider pode apontar para um arquivo XML ou conter dados XML
embutidos. Em nosso exemplo, os dados do funcionário são armazenados no arquivo Employees.xml e, portanto, a propriedade
Source do XmlDataProvider é definida como Employees.xml. A chave especifica um nome pelo qual um recurso é identificado,
EmployeeData neste caso. A propriedade XPath especifica uma consulta XPath que nos fornece os dados desejados. Nesse
caso, queremos preencher as linhas da grade com dados dos nós <employee>. Portanto, a consulta XPath é definida como /
Employees/employee.
Arraste e solte um controle DataGrid da caixa de ferramentas e altere seu XAML (você pode fazer isso usando o painel
Editor XAML do designer) para se parecer com a Listagem 12-4.
341
Machine Translated by Google
<DataGridTextColumn Header="Notas"
Binding="{Binding XPath=notas}" />
</DataGrid.Columns> </
DataGrid>
A propriedade DataContext do DataGrid é definida como XmlDataProvider (recurso estático significa simplesmente que
o recurso em consideração é resolvido no momento do carregamento e não no tempo de execução).
Observe também que o DataGrid tem cinco colunas conforme definido pelos elementos DataGridTextColumn. A coluna
EmployeeID é vinculada ao atributo employeeid dos elementos <employee> e, portanto, sua propriedade de vinculação é
configurada para usar a consulta XPath @employeeid. Da mesma forma, as outras quatro colunas são dados vinculados aos
elementos firstname, lastname, homephone e notes.
ÿ Observação A sintaxe de vinculação de dados usada pelo WPF pode parecer um pouco estranha se você nunca trabalhou com o WPF antes.
Embora não entremos nos detalhes dessa sintaxe neste livro, você pode ler a documentação do MSDN para saber mais.
Depois que a marcação XAML estiver pronta, execute o aplicativo e veja se o DataGrid está preenchido com os
dados do funcionário.
ÿ Observação Seus aplicativos ASP.NET podem usar Web Forms ou MVC. Você também pode usar a nova estrutura - ASP.
NET Core — para criar aplicativos da Web. A discussão e os exemplos discutidos nas seções a seguir usam ASP.NET Web Forms. Meu
objetivo é apenas mostrar como o ASP.NET usa XML e a sintaxe baseada em XML em vários lugares. Então, quando digo ASP.NET, quero
Os controles de servidor são wrappers orientados a objetos sobre os elementos HTML tradicionais. Eles são processados
pelo ASP.NET no lado do servidor, por isso são chamados de controles de servidor. Eles fornecem muitas vantagens sobre os
controles HTML tradicionais, incluindo funcionalidade avançada, vinculação de dados, recursos orientados a objetos e muitos
outros. Um site ASP.NET é uma coleção de formulários da Web e recursos relacionados, como imagens, arquivos JavaScript e
componentes compilados.
342
Machine Translated by Google
XML e ASP.NET
ASP.NET usa XML em vários lugares. Algumas das principais áreas onde o XML é usado extensivamente são as seguintes:
•Controle de XML
•Configuração do site
Conforme mencionado anteriormente, os formulários da Web do ASP.NET consistem principalmente em marcação de controle de
servidor e HTML. A marcação de controle do servidor é, na verdade, um vocabulário especial de XML. Cada controle de servidor tem
um nome de marca predefinido, prefixo de marca e atributos. Alguns controles de servidor são elementos vazios (eles não contêm
nenhum elemento filho), enquanto outros podem conter marcação ou texto.
Para entender melhor os controles do servidor, você desenvolverá um site ASP.NET com um formulário da Web. O formulário
da Web representa uma página típica de Fale Conosco. A página será usada pelos usuários finais para entrar em contato com você em
caso de dúvidas, feedback ou comentários sobre seu site.
Criando um site
Para criar um novo site usando o Visual Studio, você precisa escolher a opção de menu File ÿ New ÿ Web Site.
Esta opção abre a caixa de diálogo New Web Site mostrada na Figura 12-6.
343
Machine Translated by Google
O modelo ASP.NET Web Forms Site permite que você crie um site. A lista suspensa do local da Web indica o local de destino
onde o site da Web será criado. Possíveis locais são sistema de arquivos, servidor web (HTTP) e pasta FTP (FTP). Em nosso
exemplo, escolheremos o sistema de arquivos. O caminho especificado após o menu suspenso Localização é a pasta de destino onde o
site será criado. Certifique-se de selecionar Visual C# como o idioma. Clique no botão OK para criar o site. O Visual Studio criará o site
com uma determinada estrutura de pastas e também haverá um formulário da Web chamado Default.aspx.
ÿ Nota Na maioria dos nossos exemplos, não precisamos de vários itens criados pelo modelo de projeto padrão. Você pode
simplesmente removê-los do projeto para reduzir a desordem. Você também pode usar o modelo de projeto de site vazio
do ASP.NET para criar um projeto vazio e adicionar os itens conforme necessário.
344
Machine Translated by Google
O Web Form consiste em muitos controles de servidor, como Label, TextBox, DropDownList, RadioButtonList,
Button, RequiredFieldValidator, RegularExpressionValidator e ValidationSummary.
Para começar a criar o formulário da Web, use as seguintes etapas:
1. Arraste e solte um controle Label na primeira linha da tabela. Defina sua propriedade Text como
Contate-nos. Defina sua propriedade Font para sua escolha. Além disso, arraste e solte um controle
HTML de regra horizontal abaixo do rótulo Fale conosco.
3. Arraste e solte sete controles Label nas linhas 3 a 9. Defina suas propriedades Text como Your
Name :, Your Email :, Contacting For :, Subject :, Message :, Web Site (Opcional) :, e You
represent :, respectivamente .
4. Arraste e solte um controle TextBox depois de Your Name, Your Email, Subject,
Rótulos de mensagem e site da Web (opcional), respectivamente. Defina a propriedade TextMode
da caixa de texto Message como MultiLine.
7. Arraste e solte um controle Button na linha 10 e defina sua propriedade Text como Submit.
8. Arraste e solte um controle Label na linha 11 e defina sua propriedade Text como uma string em branco.
Este rótulo será usado para exibir uma mensagem de sucesso para o usuário.
9. Selecione o controle DropDownList. Abra sua janela de propriedades e localize a propriedade Items.
Adicione quatro itens na lista suspensa: selecione, cotação de vendas, problema técnico e outros.
10. Selecione o controle RadioButtonList. Abra sua janela de propriedades e localize a propriedade
Items. Adicione dois itens no RadioButtonList: Individual e Company.
345
Machine Translated by Google
Nosso design de formulário da Web acabou. Na parte inferior do Web Form Designer, você encontrará uma guia
chamada Source. Clique nele para ver a marcação gerada para o nosso Web Form. A marcação relevante de Default.aspx
é mostrada na Listagem 12-5.
<!DOCTYPE html>
...
<form id="form1" runat="servidor">
...
<asp:Label ID="Label1" runat="servidor" Font-Names="Arial"
Font-Size="X-Large" Text="Fale Conosco">
</asp:Label>
...
<asp:ValidationSummary ID="ValidationSummary1" runat="servidor" />
...
<asp:Label ID="Label2" runat="server" Text="Seu nome:"></asp:Label>
...
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator
ID="RequiredFieldValidator1"
runat="servidor" ControlToValidate="TextBox1"
Display="Dinâmico"
ErrorMessage="Por favor, digite seu nome">*
</asp:RequiredFieldValidator>
...
<asp:Label ID="Label3" runat="server" Text="Seu e-mail :"></asp:Label>
...
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox>
<asp:RequiredFieldValidator
ID="RequiredFieldValidator2" runat="servidor"
ControlToValidate="TextBox2"
346
Machine Translated by Google
Display="Dinâmico"
ErrorMessage="Por favor, digite seu e-mail">*
</asp:RequiredFieldValidator>
<asp:RegularExpressionValidator
ID="RegularExpressionValidator1"
runat="server"
ControlToValidate="TextBox2"
Display="Dinâmico"
ErrorMessage="Por favor, digite um endereço de e-mail válido"
ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">*
</asp:RegularExpressionValidator></td>
...
<asp:Label ID="Label4" runat="server" Text="Contactando para:"></asp:Label>
...
<asp:DropDownList ID="DropDownList1" runat="server">
<asp:ListItem Value=" Selecione ">Selecione</asp:ListItem> <asp:ListItem
Value=" Cotação de vendas ">Cotação de vendas</asp :ListItem> <asp:ListItem
Value=" Problema técnico ">Problema técnico</asp:ListItem> <asp:ListItem Value="
Other ">Outro</asp:ListItem> </asp:DropDownList> <asp:RequiredFieldValidator ID
="RequiredFieldValidator3" runat="servidor"
ControlToValidate="DropDownList1" Display="Dinâmico"
ErrorMessage="Por favor, selecione um motivo para entrar em contato conosco"
InitialValue="PS">* </
asp:RequiredFieldValidator>
...
<asp:Label ID="Label5" runat="server" Text="Subject :"></asp:Label>
...
<asp:TextBox ID="TextBox3" runat="server" Columns="37"></asp:TextBox>
<asp:RequiredFieldValidator
ID="RequiredFieldValidator4" runat="servidor" ControlToValidate="TextBox3"
Display="Dynamic" ErrorMessage="Por favor, insira o
assunto">* </asp:RequiredFieldValidator>
...
<asp:Label ID="Label6" runat="server" Text="Mensagem:"></asp:Label>
...
<asp:TextBox ID="TextBox4" runat="servidor" Columns="30" Linhas="3"
TextMode="MultiLine"></asp:TextBox>
<asp:RequiredFieldValidator
ID="RequiredFieldValidator5" runat="servidor" ControlToValidate="TextBox4"
Display="Dynamic" ErrorMessage="Por favor, digite a
mensagem">* </asp:RequiredFieldValidator>
...
<asp:Label ID="Label7" runat="server" Text="Web Site (Opcional) :"></asp:Label>
...
<asp:TextBox ID="TextBox5" runat="server"></asp:TextBox>
<asp:RegularExpressionValidator ID="RegularExpressionValidator2"
runat="server" ControlToValidate="TextBox5"
Display="Dynamic" ErrorMessage="Insira um URL válido"
ValidationExpression="http(s)?://([\w-]+\.)+[\w-]+(/[\w- ./?%&=]*)?"> *</
asp :RegularExpressionValidator>
...
347
Machine Translated by Google
...
<asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Enviar" />
...
<asp:Label ID="Label9" runat="server" Font-Bold="True" ForeColor="Red"></asp:Label>
...
</html>
Observe a marcação com atenção. No topo, você tem uma diretiva chamada @Page. Uma diretiva fornece
informações sobre alguma entidade para o mecanismo de processamento ASP.NET. A diretiva @Page fornece detalhes sobre o
formulário da Web atual, como linguagem de codificação, nome do arquivo de código e a classe do arquivo de código que representa
esse formulário da Web.
Em seguida, há uma declaração <!DOCTYPE> que indica que este documento é um documento HTML5.
Se você observar melhor, notará que cada controle de servidor é representado por uma marcação XML especial.
Por exemplo, um controle Label é representado por uma marca <asp:Label> e um controle TextBox é representado por uma
marca <asp:TextBox>. A parte antes dos dois pontos (:) — em outras palavras, asp — é chamada de prefixo de tag. A parte
após os dois pontos — Label ou TextBox — é chamada de nome de tag.
Cada controle de servidor tem seu atributo ID definido como um valor exclusivo. A ID de um controle é usada para
acessá-lo programaticamente. Da mesma forma, todo controle de servidor possui um atributo runat, que deve ter o valor server
e indica que a tag é uma marcação de controle de servidor. Você também observará que todas as propriedades definidas por
meio da janela Propriedades são representadas como atributos ou elementos filho na marcação de controle do servidor. Assim,
a marcação de controle do servidor ASP.NET é um vocabulário especial de XML.
Escrevendo Código
Nesta seção, você escreverá algum código para que, quando um usuário inserir dados válidos e clicar no botão Enviar,
os dados sejam enviados por e-mail para você. Para atender a esse requisito, você precisa manipular o evento Click do
botão Enviar. Clique duas vezes em Enviar e você será levado ao arquivo code-behind (Default.aspx.cs), onde poderá
escrever o código. A Listagem 12.6 mostra a classe Web Form e o esqueleto do manipulador de eventos Click. Vamos
preencher isso a seguir.
}
}
O arquivo code-behind contém uma classe parcial chamada _Default que herda de System.Web.
Classe base UI.Page, que fornece funcionalidade básica para seu Web Form. Lembre-se que a diretiva @Page possui
um atributo chamado Inherits que se refere a esta classe. A classe _Default é marcada como parcial porque somente
quando o arquivo de marcação (.aspx) e o arquivo code-behind (.cs) são combinados, a classe completa é gerada. Esse
348
Machine Translated by Google
a mesclagem ocorre em tempo de execução e é tarefa do ASP.NET Framework. Além disso, observe vários namespaces que
são importados na parte superior do arquivo code-behind. Todos os namespaces que começam com System.Web estão
localizados fisicamente em um assembly, System.Web.dll. Este é o assembly principal para ASP.NET Web Forms.
ÿ Observação Uma discussão completa dos controles de servidor ASP.NET e conceitos relacionados está além do
escopo deste livro. Se você estiver interessado no desenvolvimento de aplicativos da Web ASP.NET usando Web Forms,
Para codificar a funcionalidade, você precisa importar dois namespaces: System.Net e System.Net.Mail.
O último namespace fornece classes para enviar e-mails. A Listagem 12-7 mostra o manipulador de eventos Click depois de
adicionar o código necessário.
O código declara um objeto da classe SmtpClient. A classe SmtpClient permite enviar e-mails
baseado em protocolo de transferência de correio simples (SMTP). O construtor da classe SmtpClient aceita o endereço
IP ou o nome da máquina usada para operações SMTP. Em nosso exemplo, supõe-se que você esteja usando uma
instalação local do IIS para enviar e-mails e, portanto, localhost é passado como parâmetro. A propriedade Credentials da
classe SmtpClient indica as credenciais de rede de um usuário para autenticar o remetente. A propriedade
DefaultNetworkCredentials da classe CredentialCache indica as credenciais de autenticação do usuário atual do Windows.
Depois que as credenciais forem definidas, uma nova MailMessage será criada. A classe MailMessage representa
uma mensagem de email. As propriedades From e To desta classe representam o remetente e o destinatário, respectivamente,
e são do tipo MailAddress (certifique-se de alterar o endereço de e-mail para se adequar à sua configuração). As propriedades
Assunto e Corpo indicam o assunto e o corpo do e-mail, respectivamente. Todas essas propriedades são atribuídas usando
os valores inseridos em vários controles de formulário da Web. Finalmente, o método Send() da classe SmtpClient envia a
MailMessage fornecida para um ou mais destinatários. Uma mensagem de sucesso é exibida em um rótulo informando ao
usuário que a mensagem foi recebida.
349
Machine Translated by Google
da Web que você acabou de concluir, você precisa escolher a opção de menu Debug ÿ Start Debugging (ou apenas pressionar F5). O Visual
Studio iniciará o servidor Web de desenvolvimento em segundo plano e hospedará seu site nele. Ele também abrirá um navegador e carregará o
Default.aspx. A Figura 12-8 mostra o padrão. aspx Web Form no navegador.
Se você tentar clicar no botão Enviar sem inserir nenhum valor, deverá ver um erro de validação
mensagens, conforme mostrado na Figura 12-9.
350
Machine Translated by Google
Observe como o ValidationSummary exibe uma lista coletiva de mensagens de erro, enquanto um asterisco é exibido
no lugar de controles de validação individuais. Se você inserir valores válidos nos controles e clicar em Enviar, o código
enviará um e-mail para o seu endereço de e-mail e exibirá uma mensagem de sucesso, conforme mostrado na Figura 12-10.
ÿ Observação Como esse foi o primeiro exemplo de ASP.NET, descrevi todas as etapas necessárias para desenvolver e executar o
aplicativo. Agora que você está familiarizado com os projetos de sites da Web, os exemplos futuros se concentram no tópico principal
351
Machine Translated by Google
A tag raiz do documento é <node>, que contém ainda várias tags <node>. Cada <nó>
O elemento representa um nó do controle TreeView e possui dois atributos, text e url:
•O atributo url aponta para uma URL onde o usuário deve ser navegado.
O aninhamento dos elementos <node> decide o aninhamento do TreeView renderizado. Assim, o nó raiz do
TreeView será Home. O nó inicial terá quatro filhos imediatos: Produtos, Serviços, Sobre nós e Fale conosco. Da mesma
forma, os nós Produtos e Serviços terão três filhos cada.
Agora arraste e solte um controle XmlDataSource da caixa de ferramentas para o Web Form Designer. Defina
sua propriedade DataFile como Navigation.xml. A propriedade DataFile aponta para um arquivo XML que fornecerá
dados para o controle de fonte de dados XML. Em seguida, arraste e solte um controle TreeView no formulário da Web
e defina sua propriedade DataSourceID como a ID do controle XmlDataSource que você acabou de configurar. Agora
localize a propriedade DataBindings do TreeView e abra o TreeView DataBindings Editor (consulte a Figura 12-11).
Na área Ligações de Dados Disponíveis, você verá todos os nós em cada nível do arquivo XML. Selecione os
nó em cada nível e clique no botão Adicionar. Agora você terá três entradas na área Selected Data Bindings. Selecione
a primeira vinculação de dados e defina suas propriedades TextField e NavigateUrlField como text e url, respectivamente.
Como você deve ter adivinhado, text e url são os atributos do elemento <node>. A propriedade DataMember indica o
nome do elemento do documento XML que está fornecendo os dados e é automaticamente definido como nó. Repita o
mesmo processo para as duas vinculações de dados restantes. A Listagem 12-9 mostra a marcação completa de
Default.aspx.
352
Machine Translated by Google
<!DOCTYPE html>
DataFile="~/Navigation.xml"> </
asp:XmlDataSource> <asp:TreeView ID="TreeView1"
runat="server" AutoGenerateDataBindings="False"
DataSourceID="XmlDataSource1" Font-Bold="True" Font-Size="Large"
ShowLines="True">
<DataBindings>
<asp:TreeNodeBinding DataMember="node" NavigateUrlField="url"
TextField="text" />
<asp:TreeNodeBinding DataMember="node" NavigateUrlField="url"
TargetField="text" />
<asp:TreeNodeBinding DataMember="node" NavigateUrlField="url"
TargetField="texto" />
</DataBindings>
</asp:TreeView> </
form> </body> </html>
Como você pode ver, o controle de fonte de dados XML e o controle TreeView são representados pelas
marcas de marcação <asp:XmlDataSource> e <asp:TreeView>, respectivamente. Cada ligação de nó TreeView é
representada por uma tag <asp:TreeNodeBinding>. Os atributos DataMember, NavigateUrlField e TextField do
elemento TreeNodeBinding representam as propriedades que você atribuiu anteriormente.
Agora execute o site da Web como antes e veja como o controle TreeView renderiza os vários nós com base em
a estrutura especificada no arquivo Navigation.xml. A Figura 12-12 mostra um exemplo de execução do Web Form.
353
Machine Translated by Google
Aplicando Transformações
No exemplo anterior, você forneceu o arquivo XML como está para o controle de fonte de dados XML. E se você quiser
aplicar uma transformação XSLT ao arquivo XML e, em seguida, vinculá-lo ao TreeView? Felizmente, o controle da fonte de
dados XML tem um recurso integrado para fazer exatamente isso: a propriedade TransformFile, que aponta para um arquivo
XSLT. Antes de fornecer os dados XML para outros controles, como um TreeView, a folha de estilo XSLT é aplicada aos dados
XML e, em seguida, os dados transformados são transmitidos. Suponha que você vinculou um controle TreeView a um
documento XML, conforme mostrado na Listagem 12-10.
354
Machine Translated by Google
Como você pode ver, este documento XML usa o elemento <MenuItem> para representar um nó TreeView. Os
atributos Title e URL representam as propriedades Text e NavigateUrlField de nós TreeView individuais.
Agora suponha que, por algum motivo, você queira vincular o mesmo TreeView a outro documento XML, conforme
mostrado na Listagem 12-11.
Você deve ter notado que este é o mesmo arquivo XML que usamos no exemplo anterior. Para atender à mudança,
você precisa reconfigurar as ligações de dados do nó TreeView para se adequar ao novo documento XML. Se a ligação de
dados subjacente tiver um aninhamento complexo, isso pode não ser uma tarefa fácil. Essa situação pode ser evitada se
você aplicar uma folha de estilo XSLT ao XML da Listagem 12-11 e transformá-la para corresponder ao XML da Listagem
12-10. A folha de estilo XSLT que pode fazer essa transformação é mostrada na Listagem 12-12.
Listagem 12-12. Uma folha de estilo XSLT para transformar a nova marcação XML
</xsl:template>
<xsl:template match="nó">
<xsl:element name="MenuItem">
<xsl:nome do atributo="Título">
<xsl:value-of select="@text"/>
</xsl:attribute>
<xsl:attribute name="URL">
<xsl:value-of select="@url"/> </
xsl:attribute> <xsl:apply-templates /> </
xsl:element>
</xsl:template> </
xsl:stylesheet>
355
Machine Translated by Google
A folha de estilo transforma um elemento <node> em um elemento <MenuItem>. Além disso, ele transforma os
atributos text e url nos atributos Title e URL, respectivamente.
Para testar como a propriedade TransformFile funciona, você precisa modificar o exemplo anterior seguindo estas etapas:
Você observará que, embora a propriedade DataFile ainda seja Navigation.xml, as vinculações de dados mostradas
são de acordo com a transformação especificada na folha de estilo XSLT. Se você executar o Web Form novamente, a
saída deve ser a mesma da Figura 12-12 anterior.
356
Machine Translated by Google
357
Machine Translated by Google
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" > <head
runat="server"> <title>Página sem título</title> </head>
<body> < form id="form1" runat="servidor">
<asp:XmlDataSource ID="XmlDataSource1" runat="servidor"
DataFile="~/Navigation.xml"></asp:XmlDataSource> <asp:Menu
ID="Menu1" runat="server" BackColor="#E3EAEB"
DataSourceID="XmlDataSource1"
<DataBindings>
<asp:MenuItemBinding DataMember="node" NavigateUrlField="url"
TextField="text" />
<asp:MenuItemBinding DataMember="node" NavigateUrlField="url"
TextField="text" />
<asp:MenuItemBinding DataMember="node" NavigateUrlField="url"
TextField="texto" />
</DataBindings> </
asp:Menu> </form> </
body> </html>
Como você pode ver, o controle Menu é representado pela marca de marcação <asp:Menu>. O <DataBindings>
A seção define uma ou mais vinculações de dados, em que cada vinculação de dados é representada
por um elemento <asp:MenuItemBinding>. A execução do Web Form deve renderizar o menu conforme mostrado na Figura 12-16.
358
Machine Translated by Google
A Figura 12-17 mostra a estrutura de diretórios de um exemplo de site da Web. A página inicial (Default.aspx) e a página
Fale Conosco (contact.aspx) residem na pasta raiz do site. Existem duas subpastas chamadas Produtos e Serviços. Cada um
deles contém dois formulários da Web—Product1.aspx e Product2.aspx, e Service1.aspx e Service2.aspx—respectivamente. Agora
vamos representar a estrutura do site usando um mapa do site.
Crie um novo site usando o Visual Studio. Adicione um novo arquivo de mapa do site usando a caixa de diálogo Adicionar novo
item (consulte a Figura 12-18). Nomeie o arquivo de mapa do site Web.sitemap.
359
Machine Translated by Google
O arquivo de mapa do site contém um conjunto de tags e atributos predefinidos. O nó raiz de um arquivo de
mapa do site é <siteMap>. Além disso, contém várias tags <siteMapNodes> dependendo da estrutura do seu site. A
tag <siteMapNode> possui quatro atributos importantes. Eles estão listados na Tabela 12-1.
360
Machine Translated by Google
Atributo Descrição
título Indica o título da página. Esse atributo geralmente é usado por controles de navegação para exibir
o título da URL.
url Indica a URL da página que este nó representa.
descrição Especifica a descrição da página de destino. Você pode usar esta descrição para mostrar dicas de
ferramentas.
funções Ao usar a filtragem de segurança, esse atributo especifica as funções que têm permissão para
acessar essa página.
ÿ Observação A filtragem de segurança é um recurso que implementa a segurança baseada em funções renderizando apenas
os nós permitidos para o usuário atual. Em outras palavras, um determinado <siteMapNode> estará acessível a um usuário
apenas se a função do usuário for especificada no atributo de funções do elemento <siteMapNode> .
Os arquivos de mapa do site são frequentemente usados para renderizar algum tipo de estrutura de navegação. Há duas maneiras
comuns de consumir o arquivo de mapa do site que você acabou de criar:
• Em um controle SiteMapPath
• Em um controle SiteMapDataSource
SiteMapPath permite renderizar o que geralmente é chamado de breadcrumbs. A Figura 12-19 mostra o que são breadcrumbs.
O controle SiteMapPath exibe vários níveis de navegação. Você pode clicar nos níveis pai ou raiz
para navegar de volta ou para o nível superior. Antes de nos aprofundarmos nos detalhes, vamos primeiro criar a estrutura de
diretórios e os formulários da Web necessários. Comece adicionando duas pastas ao site chamado Produtos e Serviços. Adicione
os Web Forms conforme mostrado na Tabela 12-2.
361
Machine Translated by Google
Produto1.aspx Produtos
Produto2.aspx Produtos
Service1.aspx Serviços
Service2.aspx Serviços
Agora arraste e solte um controle SiteMapPath em cada Web Form. Execute Service2.aspx no navegador,
e você deve ver algo semelhante à Figura 12-19. O controle SiteMapPath renderiza automaticamente trilhas de
navegação para o formulário da Web atual com base em sua localização no arquivo de mapa do site.
não se limita apenas ao controle SiteMapPath. Você também pode anexar o mapa do site a controles de navegação,
como um TreeView. Neste exemplo, usaremos o mesmo arquivo de mapa do site para vincular a um TreeView.
Crie um novo site usando o Visual Studio. Adicione o mesmo arquivo Web.sitemap a ele. Desta vez, arraste e solte
um controle SiteMapDataSource no Web Form. O controle SiteMapDataSource seleciona automaticamente o arquivo
Web.sitemap e o fornece a outros controles. Além disso, arraste e solte um controle TreeView e defina sua propriedade
DataSourceID para o atributo ID do SiteMapDataSource. É isso. Execute o formulário da Web e ele deve se parecer com a
Figura 12-20.
362
Machine Translated by Google
Listagem 12-15. Arquivo XML que fornece dados para o controle XML
<firstname>Nancy</firstname>
<lastname>Davolio</lastname>
<homephone>(206) 555-9857</homephone>
<notes> <![CDATA[A educação inclui
bacharelado em psicologia pela Colorado State University em 1970.
Ela também completou "The Art of the Cold Call". Nancy é membro do Toastmasters International.]]> </
notes> </employee> <employee employeeid="2">
<firstname>Andrew</firstname>
<lastname>Fuller</lastname>
<homephone>(206) 555-9482</homephone>
<notes> <![CDATA[Andrew recebeu seu
comercial do BTS em 1974 e um Ph.D. em marketing internacional pela Universidade
de Dallas em 1981. Ele é fluente em francês e italiano e lê alemão. Ele ingressou na empresa como
representante de vendas, foi promovido a gerente de vendas em janeiro de 1992 e a vice-presidente
de vendas em março de 1993. Andrew é membro da Sales Management Roundtable, da Seattle
Chamber of Commerce e da Pacific Rim Importers Association. ]]> </notes> </employee> <employee
employeeid="3">
<firstname>Janet</firstname>
<lastname>Leverling</lastname>
<homephone>(206) 555-3412</homephone>
<notes> <![CDATA[Janet é bacharel em química
pelo Boston College (1984) .
363
Machine Translated by Google
Da mesma forma, adicione uma nova folha de estilo XSLT denominada Employees.xsl e digite a marcação mostrada
na Listagem 12-16.
<body>
<h1>Lista de Funcionários</h1>
<table border="1">
<tr>
<th>ID do funcionário</
th> <th>Nome</th>
<th>Sobrenome</th>
<th>Telefone Residencial</th>
<th>Notas</th>
</tr>
<xsl:for-each select="funcionários/funcionário">
<tr>
<td>
<xsl:value-of select="@employeeid"/> </td>
<td>
<xsl:value-of select="firstname"/>
</td>
<td>
<xsl:value-of select="lastname"/>
</td>
<td>
<xsl:value-of select="homephone"/> </td>
<td>
<xsl:value-of select="notas"/>
</td>
</tr>
</xsl:para-cada>
</table>
</body>
</html>
</xsl:template> </
xsl:stylesheet>
364
Machine Translated by Google
ÿ Observação Esses são os mesmos arquivos usados no Capítulo 6. Se desejar, você pode adicioná-los ao seu site
da Web em vez de recriá-los.
Agora arraste e solte um controle XML no formulário da Web padrão. Defina sua propriedade DocumentSource como
Employees.xml e sua propriedade TransformSource como Employees.xsl. A primeira propriedade aponta para o arquivo XML que
deve ser transformado, enquanto a última propriedade aponta para a folha de estilo XSLT que deve ser aplicada ao DocumentSource.
Agora execute o Web Form e você deverá ver algo semelhante à Figura 12-21.
Quando você instala o .NET Framework em uma máquina, um arquivo XML chamado Machine.config é criado
na pasta <installation_path>/Config. O arquivo Machine.config é o arquivo de configuração mestre e contém definições de
configuração que são aplicadas a todos os aplicativos .NET em execução nessa máquina.
Embora este arquivo esteja no formato XML e possa ser editado diretamente, você deve fazer isso com cautela. Isso é
porque qualquer alteração feita neste arquivo afetará todos os aplicativos em execução nessa máquina.
A Figura 12-22 mostra a localização de Machine.config em uma instalação de amostra.
365
Machine Translated by Google
Para substituir as configurações especificadas no arquivo Machine.config, você precisa criar arquivos de configuração
do aplicativo. Os arquivos de configuração do aplicativo também são arquivos XML contendo marcação XML especial. Para
aplicativos baseados no Windows, o arquivo de configuração do aplicativo tem o formato <exe_name>.exe.config, em que
exe_name é o nome do executável do aplicativo.
ÿ Observação Ao desenvolver aplicativos do Windows no Visual Studio, você descobrirá que há um arquivo App.config
na pasta raiz do projeto. Este arquivo contém a configuração do aplicativo de tempo de desenvolvimento. O arquivo
<exe_name>.exe. config pode ser encontrado nas pastas Bin\Debug ou Bin\Release . Você deve implantar
<exe_name>.exe.config junto com seu aplicativo e não o arquivo App.config .
Para aplicativos da web, o nome do arquivo de configuração do aplicativo deve ser web.config. Nas seções a
seguir, você aprenderá mais sobre o arquivo web.config e o vocabulário XML usado nele.
ÿ Observação Há também arquivos de configuração de segurança que armazenam a configuração relacionada à segurança de acesso ao código.
Esses arquivos devem ser alterados usando a ferramenta Code Access Security Policy (Caspol.exe). É
recomendável que você altere esses arquivos apenas se estiver ciente do impacto que eles terão na política de
segurança. Não entraremos em detalhes sobre arquivos de configuração de segurança neste livro.
366
Machine Translated by Google
<configuration>
<appSettings />
<connectionStrings />
<system.web /> </
configuration>
Como você pode ver, o nó raiz do arquivo web.config é <configuration>, e há três principais
subseções:
•A seção <connectionStrings> é usada para armazenar uma ou mais conexões de banco de dados
cordas.
Todas essas seções são opcionais. No entanto, na maioria dos casos do mundo real, você terá pelo menos a
seção <system.web>.
ÿ Observação O arquivo web.config contém muitas seções de configuração. Não é possível cobrir todas as
seções aqui. Vou discutir apenas algumas seções comumente usadas.
Herança do Web.config
O arquivo web.config exibe o que geralmente é chamado de comportamento de herança. Em um único aplicativo da web,
pode haver um ou mais arquivos web.config em pastas diferentes. As configurações de um arquivo web.config são aplicadas à
pasta na qual ele reside e a todas as subpastas. No entanto, se as subpastas contiverem um web.config próprio, as configurações
especificadas nesse web.config terão precedência.
367
Machine Translated by Google
ÿ Observação Embora o exemplo discutido aqui use a seção <appSettings> em um aplicativo da web,
<appSettings> é igualmente aplicável a aplicativos do Windows.
Abra o mesmo site escolhendo File ÿ Open ÿ Web Site no menu. Abra o arquivo web.config
e modifique a seção <appSettings>, conforme mostrado na Listagem 12-18.
<appSettings>
<add key="host" value="localhost"/> <add
key="email" value="user@localhost"/> </appSettings>
A seção <appSettings> pode conter um ou mais elementos <add>. O elemento <add> tem dois atributos:
•O atributo chave define uma chave com a qual o valor será acessado no código.
Em nosso exemplo, definimos duas chaves: host e email. A primeira chave armazena o valor do host SMTP e a última armazena
seu endereço de e-mail. Agora abra o code-behind do Web Form e modifique o manipulador de eventos Click do botão Submit,
conforme mostrado na Listagem 12-19.
368
Machine Translated by Google
msg.Body =
"[" + DropDownList1.SelectedItem.Text + "]" + TextBox4.Text + "\r\n" +
TextBox1.Text + "\r\n" + TextBox5.Text; cliente.Enviar(msg);
Label9.Text = "Sua mensagem foi enviada. Obrigado!"; }
Observe o código marcado em negrito. O código usa uma classe chamada ConfigurationManager, que reside no namespace
System.Configuration. Por padrão, System.Configuration é importado no code-behind.
A propriedade AppSettings da classe ConfigurationManager expõe toda a seção <appSettings> como uma NameValueCollection.
Você pode acessar valores individuais usando um índice ou um nome de chave, embora seja mais comum acessá-los usando nomes
de chave.
O código recupera os valores de duas chaves — host e email — e os armazena em uma variável de string. O
O construtor da classe SmtpClient agora aceita o valor armazenado na variável de string do host em vez de um valor embutido
em código. Da mesma forma, o método Add() aceita o valor armazenado na variável de string de e-mail e não um valor embutido em
código. Se você executar o aplicativo, deverá obter os resultados como antes, mas agora poderá alterar o nome do host e o endereço
de e-mail sem tocar no código-fonte.
conexão com o banco de dados fora do código-fonte é provavelmente a tarefa de configuração mais comum. O arquivo de
configuração possui uma seção especial para armazenar strings de conexão com o banco de dados chamada <connectionStrings>.
A seção <connectionStrings> permite armazenar uma ou mais strings de conexão de banco de dados que podem ser recuperadas
posteriormente em seu código. Para recuperar as strings de conexão armazenadas na seção <connectionStrings>, você precisa
novamente usar a classe ConfigurationManager.
Para ilustrar o uso da seção <connectionStrings>, você desenvolverá um formulário da Web simples para listar funcionários.
O Web Form exibirá uma lista de funcionários em um controle GridView. Para começar, crie um novo site usando o Visual Studio
como antes.
ÿ Observação Embora o exemplo discutido aqui use a seção <connectionStrings> em um aplicativo da web,
<connectionStrings> é igualmente aplicável a aplicativos do Windows.
Abra o arquivo web.config no IDE e modifique a seção <connectionStrings>, conforme mostrado na Listagem 12-20.
<connectionStrings>
<adicionar nome="vento norte"
connectionString="fonte de dados=.;catálogo inicial=Northwind; segurança
integrada=true" providerName="System.Data.SqlClient"/>
</connectionStrings>
369
Machine Translated by Google
A seção <connectionStrings> pode conter um ou mais elementos <add>, cada um definindo uma string de conexão com o banco
de dados:
•O atributo name do elemento <add> define um nome para essa string de conexão.
Esse nome é usado posteriormente para acessar a string de conexão.
• Finalmente, o atributo providerName indica o provedor de dados .NET que pode ser usado para se
comunicar com o banco de dados.
Agora abra o formulário da Web padrão e arraste e solte um controle GridView nele. Em seguida, digite o código
mostrado na Listagem 12-21 no evento Page_Load do Web Form.
string strConn =
ConfigurationManager.ConnectionStrings["northwind"].ConnectionString; SqlDataAdapter da =
O código usa a classe ConfigurationManager para recuperar o valor da string de conexão. A coleção
ConnectionStrings pode ser acessada usando um índice ou um nome de string de conexão. Em nosso exemplo, nós o
acessamos com um nome.
Cada string de conexão armazenada na seção <connectionStrings> é representada por uma classe
ConnectionStringSettings e a propriedade ConnectionString dessa classe retorna a string de conexão real. A cadeia de conexão
é então usada como o segundo parâmetro do construtor SqlDataAdapter, sendo o primeiro parâmetro a consulta SELECT.
Um DataSet é então preenchido usando o método Fill() da classe SqlDataAdapter. O DataSet assim criado atua como um
DataSource para o controle GridView. O método DataBind() do controle GridView vincula os dados do DataSet ao GridView. Se você
executar o Web Form depois de escrever o código, deverá ver algo semelhante à Figura 12-23.
370
Machine Translated by Google
sites da Web, você pode precisar garantir que as páginas sejam acessíveis apenas a usuários válidos do aplicativo. Além disso,
você pode querer que apenas os usuários pertencentes a determinada função (digamos, Administrador) tenham permissão para
acessar as páginas. O último é freqüentemente chamado de segurança baseada em função. Para implementar esses recursos de segurança,
você usa autenticação e autorização.
Autenticação é um processo pelo qual você decide se uma pessoa é um usuário válido do aplicativo.
Normalmente, essa validade é determinada por uma combinação de nome de usuário e senha. A autorização é um processo pelo qual você
decide se um usuário autenticado pode acessar uma determinada página ou recurso. Uma maneira comum de autorizar usuários é adicioná-
los a uma ou mais funções.
O arquivo web.config tem duas seções—<autenticação> e <autorização>—que permitem configurar os respectivos aspectos de
segurança para um site. Como exemplo, vamos construir um site da Web simples que autentique um usuário e exiba uma mensagem
de boas-vindas após uma tentativa de login bem-sucedida.
ÿ Observação Para manter as coisas simples, não implementaremos autenticação baseada em banco de dados e segurança
baseada em função neste exemplo. Em um caso realista, você poderia usar ASP.NET Membership and Roles ou, melhor
ainda, ASP.NET Identity para desenvolver um esquema completo de autenticação e autorização para seu site.
Crie um novo site usando o Visual Studio como antes. Em seguida, abra o arquivo web.config no IDE e adicione as seções
<authentication> e <authorization>, conforme mostrado na Listagem 12-22.
371
Machine Translated by Google
<modo de autenticação="Formulários">
<forms loginUrl="~/login.aspx" defaultUrl="~/
default.aspx"> </forms> </authentication>
<authorization> <deny users="?"/>
</autorização>
A marcação mostrada na Listagem 12-22 define o atributo mode da seção <authentication> para Forms.
O atributo mode pode assumir um destes valores—None, Forms, Windows, Passport e Federated. A autenticação de
formulários é uma forma muito comum de autenticação em que uma página de login é apresentada ao usuário, o usuário
insere algum nome de usuário e senha e, em seguida, essas credenciais são validadas em algum armazenamento de dados
(como o SQL Server).
A seção <authentication> contém o elemento <forms> que configura a autenticação de formulários.
O atributo loginUrl da marca <forms> indica que Login.aspx é a página de login do aplicativo.
Caso algum usuário tente acessar outras páginas sem fazer login primeiro, ele será redirecionado para esta página. O
atributo defaultUrl da tag <forms> indica que, após uma tentativa de login bem-sucedida, o usuário pode ser redirecionado
para a página padrão.
A seção <autorização> contém o elemento <deny>. O atributo users da tag <deny> é definido como ?, indicando que
usuários anônimos não devem ter permissão para acessar o site.
Agora adicione dois Web Forms ao projeto — Default.aspx e Login.aspx. Em seguida, abra Login.aspx e arraste e solte
um controle Login nele da caixa de ferramentas. Como você deve ter adivinhado, o controle Login permite capturar facilmente
o nome de usuário e a senha de um usuário. Nesse estágio, Login.aspx deve se parecer com a Figura 12-24.
Agora clique duas vezes no controle Login para acessar o manipulador de eventos Authenticate. O evento Autenticar
handler é um local para escrever seu código que valida as credenciais do usuário. Em um caso do mundo real, você teria um
acesso ao banco de dados envolvido aqui. Para simplificar, nosso exemplo usa um nome de usuário e senha embutidos em
código. O manipulador de eventos Authenticate é mostrado na Listagem 12-23.
372
Machine Translated by Google
}
outro {
e.Authenticated = false;
}
}
Depois que o cookie de autenticação é emitido, o código redireciona o usuário para a página padrão. Observe como a
propriedade DefaultUrl da classe FormsAuthentication é usada para recuperar o defaultUrl definido anteriormente no arquivo de
configuração.
Se o nome de usuário e a senha não corresponderem aos valores especificados, a propriedade Authenticated da
classe AuthenticateEventArgs será definida como false. Dessa forma, o controle Login exibirá uma mensagem de erro ao
usuário.
Em seguida, abra o arquivo Default.aspx e arraste e solte dois controles Label e um controle Button sobre ele. Defina a
propriedade Text do Button como Logout. A página padrão exibirá uma mensagem de boas-vindas ao usuário. Para especificar
essa mensagem, escreva o código mostrado na Listagem 12-24 no manipulador de eventos Page_Load.
O código recupera o nome do usuário conectado no momento usando Page.User.Identity.Name e forma uma mensagem
de boas-vindas com base nele. O código então verifica a propriedade IsAuthenticated do objeto Request.
Esta propriedade indica se uma solicitação é autenticada. Se a solicitação for autenticada, uma mensagem é exibida no
segundo Label.
Agora, clique duas vezes no controle Button para entrar em seu manipulador de eventos Click. Escreva o código mostrado
na Listagem 12-25 no manipulador de eventos Click.
373
Machine Translated by Google
FormsAuthentication.SignOut();
Response.Redirect(FormsAuthentication.LoginUrl);
}
O código chama o método SignOut() da classe FormsAuthentication. O método SignOut() remove o cookie de autenticação
emitido anteriormente. Depois que o cookie é removido, o usuário é levado de volta à página de login. Isso é feito redirecionando
o controle para a página, conforme indicado pela propriedade LoginUrl de FormsAuthentication.
Se você executar o aplicativo, será direcionado para a página de login mostrada anteriormente. Se você inserir válido
credenciais, a página padrão será exibida conforme mostrado na Figura 12-25.
Como você pode ver, a mensagem de boas-vindas exibe o nome de usuário. Você pode clicar no botão Sair para
saia e retorne à página de login.
<sessionState
mode="InProc"
timeout="2"
cookieless="true"> </
sessionState>
374
Machine Translated by Google
O elemento <sessionState> mostrado na Listagem 12-26 define o atributo mode como InProc. O atributo mode controla
onde os dados da sessão serão armazenados. Os valores possíveis são Off, InProc, StateServer, SQLServer e Custom. Se você
definir o modo como Desativado, a sessão será desativada e você não poderá armazenar nenhum dado na sessão. O valor de
InProc indica que os dados da sessão são armazenados na memória do servidor web que hospeda o aplicativo. Os modos
StateServer e SQLServer permitem que você armazene o estado da sessão fora do servidor web (não os discutiremos neste livro).
O atributo timeout define o tempo ocioso em minutos após o qual a sessão é esvaziada. O valor 2 significa que se você
armazenar alguns dados na sessão e depois não usar o aplicativo por 2 minutos, os dados serão removidos da sessão. O tempo
limite padrão da sessão é de 20 minutos.
Por padrão, as sessões requerem cookies ativados no navegador do cliente. Esse cookie normalmente armazena o
ID de uma sessão. Se você não quiser usar cookies por algum motivo, o atributo sem cookie pode ser útil.
Definir sem cookie como falso significa que a sessão não usará cookies; em vez disso, o ASP.NET adicionará algo à URL (você
verá isso em um minuto).
Agora, abra Default.aspx e arraste e solte um controle Label nele. Em seguida, adicione o código mostrado em
Listagem 12-27 para o manipulador de eventos Page_Load.
if(Sessão["timestamp"]==nulo) {
Session["timestamp"] = DateTime.Now.ToString();
}
Label1.Text = Session["timestamp"].ToString();
}
O manipulador de eventos Page_Load primeiro verifica se Session contém uma chave chamada timestamp. Se este
key não existe (o valor é nulo), os valores de data e hora atuais são armazenados na sessão.
O valor do carimbo de data/hora é então atribuído ao rótulo.
Execute o site; seu formulário da Web deve se parecer com a Figura 12-26.
Se você atualizar o navegador, verá que o carimbo de data/hora não muda. Isso indica que o valor armazenado na
sessão durante a primeira execução da página está sendo usado conforme o esperado. Agora, mantenha a janela do navegador
ociosa por 3-4 minutos e, em seguida, atualize-a novamente. Você descobrirá que o carimbo de data/hora mudou. Isso ocorre
devido ao valor de tempo limite de 2 minutos. Após 2 minutos de tempo ocioso, a chave de registro de data e hora foi removida da
sessão e, portanto, um novo registro de data e hora foi armazenado.
Observe também a barra de endereço do navegador. Você verá algum segmento de string inserido na URL. Esse é o
resultado da configuração do atributo sem cookie como verdadeiro. Devido a essa configuração, o ASP.NET adiciona um ID de
sessão à URL em vez de armazená-lo em um cookie.
375
Machine Translated by Google
FileNotFound.aspx Raiz Página de erro personalizada que é exibida para o código de erro HTTP 404
UnAuthorized.aspx Raiz Página de erro personalizada que é exibida para o código de erro HTTP 403
GlobalErrorPage.aspx Raiz Página de erro personalizada que é exibida para qualquer outro erro não
tratado no código ou de outra forma
Default.aspx Administrador
Representa a página padrão da pasta Admin
Agora projete Default.aspx da pasta raiz para se parecer com a página mostrada na Figura 12-27.
Default.aspx contém dois controles de hiperlink intitulados Ir para Pasta Admin e Ir para Arquivo Inexistente.
Defina a propriedade NavigateUrl desses controles de hiperlink como ~/admin/default.aspx e ~/notexists.aspx, respectivamente. Observe
que estamos definindo deliberadamente o NavigateUrl do segundo hiperlink para um arquivo inexistente. Agora arraste e solte um controle
LinkButton e defina sua propriedade Text como Throw Exception. Adicione o código mostrado na Listagem 12-28 em seu manipulador de
eventos Click.
376
Machine Translated by Google
O código simplesmente lança uma nova exceção. Como esta é uma exceção não tratada, teremos uma chance
para capturá-lo usando uma página de erro personalizada. Agora adicione um rótulo e um controle de hiperlink cada um em
FileNotFound. aspx, UnAuthorized.aspx e GlobalErrorPage.aspx. Defina a propriedade Text dos controles Label para uma
mensagem de erro amigável. Aponte o hiperlink para Default.aspx para que os usuários possam navegar facilmente de volta à
página inicial.
Adicione o código mostrado na listagem 12-29 no evento Page_Load do formulário da Web Admin/Default.aspx.
O código gera um HttpException com um código de status de 403 e uma mensagem de string. O HttpException
classe representa uma exceção específica de HTTP. Dessa forma, acionamos uma exceção com código de status 403
(acesso não autorizado).
Agora abra web.config no IDE e adicione a marcação mostrada na Listagem 12-30 na seção <system.web>.
A seção <customErrors> permite que você especifique páginas de erro personalizadas para seu site. O modo
atributo tem três valores possíveis:
• Se o modo estiver ativado, as páginas de erro personalizadas serão ativadas para todas as máquinas que
navegam no site.
• Se o modo estiver desativado, as páginas de erro personalizadas serão desativadas para todas as máquinas.
• Se o modo for RemoteOnly, os erros personalizados são ativados apenas para máquinas remotas que
navegam no site, mas são desativados para navegadores locais.
Durante o desenvolvimento, geralmente seu servidor da Web e o navegador serão executados no mesmo
máquina e, portanto, você deve definir o modo como On.
O atributo defaultRedirect aponta para uma página da web que deve ser exibida caso haja alguma
erro não tratado em todo o aplicativo.
A seção <customErrors> pode ter várias tags <error>. O atributo statusCode da tag <error> especifica o código de erro
HTTP no nível do servidor da web. O atributo de redirecionamento especifica a página da Web a ser exibida no caso desse erro. Em
nosso exemplo, configuramos duas páginas de erro personalizadas: uma para o código de status 403 (UnAuthorized.aspx) e a outra para
o código de status 404 (FileNotFound.aspx).
Agora execute Default.aspx e clique nos três links, um por um. Você notará que, em vez de exibir
a página de erro padrão, desta vez o ASP.NET exibe as páginas de erro personalizadas conforme especificado em web.config.
A Figura 12-28 mostra um exemplo de execução do site.
377
Machine Translated by Google
começar, você precisa criar uma biblioteca de classes chamada XmlComments.dll. Essa biblioteca de classes representa uma
calculadora matemática simples e consiste em uma única classe chamada Calculadora. A classe Calculadora permite somar,
subtrair, dividir e multiplicar números. Embora este exemplo possa parecer muito simples (e de fato é), seu objetivo aqui é
aprender a sintaxe de comentários XML.
Crie um novo projeto de biblioteca de classes no Visual Studio. Nomeie o projeto como XmlComments e a classe
Calculadora. Digite o código da Listagem 12-31 na classe Calculadora.
378
Machine Translated by Google
retornar (a - b); }
public int Divide(int a,
int b) {
retornar (a/b); }
public int Multiply(int
a, int b) {
retornar (a * b); }
A classe consiste em quatro métodos: Add(), Subtract(), Divide() e Multiply(). Cada método aceita dois
parâmetros e executa a ação correspondente neles. O resultado do cálculo é retornado ao chamador. Agora que
você tem a classe pronta, vamos adicionar comentários de documentação XML a ela.
Adicionando parágrafos
O resumo ou comentários podem consistir em vários parágrafos de texto. Cada parágrafo é representado por uma tag
<para>. Observe que uma tag <para> é sempre um elemento filho de <summary> ou <remarks>. A Listagem 12-33 mostra
o uso de uma tag <para>.
379
Machine Translated by Google
retornar (a + b); }
380
Machine Translated by Google
Adicionando listas
Sua documentação pode precisar de listas com marcadores ou numeradas. Isso pode ser feito usando três tags: <list>,
<item> e <listheader>. As tags <item> e <listheader> devem aparecer dentro da tag <list>. A tag <list> tem um atributo
chamado type que pode assumir um valor de marcador, número ou tabela. A tag <listheader> serve para fornecer um
cabeçalho para a lista. Finalmente, a tag <item> encapsula um único item da lista. A Listagem 12-37 mostra o uso
dessas tags.
381
Machine Translated by Google
...
A seção Saída da guia Construir permite especificar se a documentação XML e o nome do arquivo devem ser
gerados. Especifique o nome do arquivo como XmlComments.xml e construa a biblioteca de classes XmlComments.
Como resultado da compilação, você obterá XmlComments.dll e XmlComments.xml na pasta de saída. A Figura 12-30
mostra XmlComments.xml aberto em um navegador.
382
Machine Translated by Google
Como você deve ter adivinhado, a documentação em XML é boa, mas precisa ser convertida para um formato mais
legível, como HTML ou arquivos de ajuda. Não há uma maneira interna de fazer isso no Visual Studio. Algumas das maneiras de
gerar arquivos de documentação amigáveis incluem:
•Escrever uma folha de estilo XSLT que transforma a documentação XML em HTML.
• Usando uma ferramenta de terceiros que lê os comentários XML ou o arquivo de documentação XML e
gera documentação legível para você.
Na próxima seção, você usará a última técnica para gerar documentação a partir do XML
comentários.
Usando o Sandcastle Help File Builder para gerar arquivos de ajuda O Sandcastle Help File Builder (SHFB) é
uma ferramenta para gerar documentação a partir dos comentários XML que você aprendeu na seção anterior. Esta ferramenta
está disponível no GitHub em https://github.com/EWSoftware/SHFB.
Você pode alimentar qualquer assembly .NET para a ferramenta e gerar documentação em qualquer um dos seguintes formatos:
• Remarcação (.md)
•HTML (.htm/.html)
383
Machine Translated by Google
Para usar o SHFB, você deve instalá-lo e suas dependências. O assistente de instalação guia você por todas
as etapas.
Depois de instalar o SHFB, abra-o e crie um novo projeto SHFB selecionando File ÿ New Project. Salve o projeto
recém-criado em alguma pasta. Em seguida, clique com o botão direito do mouse em Documentation Sources no Project
Explorer e selecione o arquivo XmlComments.dll do exemplo anterior. Isso indica que você deseja gerar a documentação
deste assembly. Nesse estágio, sua janela SHFB terá a aparência mostrada na Figura 12-31.
Observe que o Documentation Sources também lista XmlComments.xml automaticamente. Na lista de formatos de
arquivo de ajuda, marque a opção Somente site. Selecionar esta opção gerará documentação em formato HTML e você
poderá visualizá-la facilmente em qualquer navegador.
Em seguida, selecione Documentation ÿ Build Project no menu e deixe a ferramenta gerar a
documentação para você. Depois que a documentação for gerada, você pode ir para a pasta Help na pasta do projeto SHFB
e iniciar Index.html em qualquer navegador. A Figura 12-32 mostra a documentação gerada para o assembly XmlComments.
384
Machine Translated by Google
Como você pode ver, o SHFB converteu seus comentários XML em páginas da Web bem formatadas e estilizadas. Tente
navegar para várias partes da documentação usando a árvore à esquerda.
Resumo
O .NET Framework não apenas permite que você trabalhe com uma variedade de tecnologias XML, mas também usa XML em
muitos lugares. Este capítulo forneceu uma introdução a essas áreas.
Você começou com aplicativos WPF, onde a interface do usuário é definida por XAML. Você também exibiu
dados de um documento XML em um DataGrid de um aplicativo WPF.
ASP.NET usa XML pesadamente para marcação e configuração de controle de servidor. Então você aprendeu sobre
os arquivos de configuração do aplicativo. Especificamente, você aprendeu a armazenar e recuperar definições de
configuração de aplicativos e strings de conexão de banco de dados. O arquivo web.config é um arquivo XML que
armazena informações de configuração do ASP.NET. Você aprendeu como configurar seu site para usar autenticação de
formulários e estado de sessão. Você também aprendeu a configurar páginas de erro personalizadas para lidar com erros
inesperados em seu site.
Por fim, você aprendeu sobre comentários XML — um recurso que pode ajudar na geração de documentação. Você
também usou o Sandcastle Help File Builder (SHFB) para converter os comentários XML em documentação HTML.
385
Machine Translated by Google
CAPÍTULO 13
Tradicionalmente, os sistemas de software usavam diferentes métodos de acesso a dados de diferentes armazenamentos
de dados. Por exemplo, para acessar dados de bancos de dados relacionais, arquivos simples e arquivos XML, os
desenvolvedores precisavam aprender e dominar diferentes modelos de objetos. Esses modelos de objetos geralmente eram
totalmente diferentes uns dos outros em termos de sintaxe de uso e filosofia subjacente. Começando com o .NET Framework 3.5,
a Microsoft introduziu a tecnologia Language Integrated Query (LINQ) que simplifica o incômodo envolvido ao consumir diferentes
fontes de dados. LINQ vem em três tipos principais: LINQ to Objects, LINQ to ADO.NET e LINQ to XML.
Este capítulo aborda os recursos do LINQ to XML junto com uma breve cartilha sobre o LINQ em geral. Lembre-se, no
entanto, que qualquer cobertura detalhada do LINQ está além do escopo deste livro. Este capítulo se restringe aos seguintes
tópicos:
• Breve introdução às expressões LINQ
A camada inferior da arquitetura LINQ consiste em fontes de dados habilitadas para LINQ. Uma fonte de dados habilitada para
LINQ normalmente é um objeto que implementa as interfaces genéricas IEnumerable<T> ou IQueryable<T>. Você pode se perguntar
como os bancos de dados relacionais e os arquivos XML implementarão essas interfaces; as classes LINQ to ADO.NET e LINQ to
XML fornecem toda a infraestrutura necessária para extrair os dados dessas fontes para uma coleção que implementa as interfaces
IEnumerable<T> ou IQueryable<T>.
O .NET Framework fornece o que é conhecido como operadores de consulta padrão que funcionam em qualquer fonte
de dados habilitada para LINQ e fornecem recursos como consulta, filtragem, classificação e agrupamento de dados.
Pode-se usar os operadores de consulta padrão diretamente nas fontes de dados LINQ. No entanto, para tornar a vida ainda
mais fácil, o C# fornece palavras-chave e construções de linguagem para executar tarefas LINQ nativamente. Isso significa que
consultar o armazenamento de dados subjacente agora é um recurso integrado à própria linguagem de programação C#. Usando
esses recursos, você pode escrever o que é conhecido como expressões de consulta LINQ ou consultas LINQ.
ÿ Observação Embora você possa usar operadores de consulta padrão para trabalhar com fontes de dados habilitadas para
LINQ, neste capítulo usarei palavras-chave da linguagem C# para escrever as consultas LINQ.
388
Machine Translated by Google
A aplicação preenche uma lista genérica de objetos Employee com dados da tabela Employees. A interface
do usuário do aplicativo consiste em uma série de caixas de combinação que listam as colunas da tabela
Employee (EmployeeID, FirstName, LastName, BirthDate e Country). Para agrupar, classificar, filtrar e selecionar
dados da lista, você pode escolher o campo desejado na caixa de combinação e clicar no botão Mostrar. Os resultados
da operação correspondente são mostrados em um controle TextBox somente leitura.
Antes de entrar nos detalhes das consultas LINQ, primeiro você precisa criar a classe Employee. Listagem 13-1
mostra o código completo da classe Employee.
389
Machine Translated by Google
while (leitor.Read()) {
}
}
A classe Employee consiste em cinco propriedades públicas, ou seja, EmployeeID, FirstName, LastName,
BirthDate e Country. O método estático GetEmployees() se conecta ao banco de dados Northwind e busca todos os
registros da tabela Employees. Os valores são então preenchidos em uma lista de objetos Employee.
Por fim, a lista é devolvida ao chamador.
Depois de criar a classe Employee, você pode chamar seu método GetEmployees() do evento Load do formulário
(consulte a Listagem 13-2).
Observe que na Listagem 13-2 a lista genérica é declarada no nível de classe porque várias funções precisam acessar
os dados. O manipulador de eventos Load do formulário simplesmente chama o método GetEmployees() e armazena os
dados retornados na lista.
txtResults.Clear(); if
(comboBox1.SelectedItem.ToString() == "País") {
390
Machine Translated by Google
OutputResults("Grupo:" + grupo.Chave);
foreach (funcionário emp no grupo) {
OutputResults(emp); }
}
}
...
}
O código primeiro decide qual campo será usado para fins de agrupamento (vemos aqui o código que trata
do campo país). Em seguida, ele forma uma expressão de consulta LINQ que agrupa os itens da lista com base
nesse campo. Observe o uso de extensões de linguagem C# para LINQ na forma das palavras-chave var, from e
group. Quando você trabalha com LINQ, o tipo de retorno das consultas geralmente é desconhecido no momento
de escrever a consulta. Para indicar esse tipo anônimo, C# usa a palavra-chave var. A consulta LINQ a seguir
indica que você deseja buscar todos os itens da coleção de funcionários e agrupá-los por valores de país. Em
tempo de execução, essa consulta LINQ retorna um dicionário.
O loop foreach externo itera por todos os itens desse dicionário. A cada iteração, a chave do item do
dicionário é exibida na caixa de texto usando o método auxiliar OutputResults() (discutido a seguir).
O loop interno foreach percorre todos os funcionários de um grupo e emite seus detalhes na caixa de texto.
A Listagem 13-3 mostra o código apenas para o campo País, mas os demais campos seguirão a mesma estrutura.
O método auxiliar OutputResults() tem duas sobrecargas, conforme mostrado na Listagem 13-4.
A primeira sobrecarga do método auxiliar OutputResults() aceita um objeto Employee e anexa seus valores de
propriedade ao conteúdo da caixa de texto. A segunda sobrecarga simplesmente aceita um valor de string e o anexa ao
conteúdo da caixa de texto.
391
Machine Translated by Google
Agora vamos implementar a funcionalidade de classificação. A operação de classificação é feita no manipulador de eventos
Click do segundo botão Mostrar. A parte do manipulador de eventos é mostrada na Listagem 13-5.
}
...
}
A cláusula orderby de uma consulta LINQ classifica os resultados com base nos campos indicados. O código
na Listagem 13-5 classifica os dados com base nos valores do País. Observe o uso da palavra-chave select que
decide o que buscar. Revisitaremos a palavra-chave select em seções posteriores. O loop foreach itera os resultados
e os emite na caixa de texto.
...
}
392
Machine Translated by Google
O código da Listagem 13-6 filtra a lista de funcionários para um país específico conforme fornecido na caixa de texto.
Nosso exemplo usa o operador == de C#. Você também pode usar o && (AND) e || (OR) para criar condições mais complexas.
Os resultados são então enviados para a caixa de texto como antes.
...
}
O código primeiro decide qual dos campos do objeto Employee deve ser selecionado. Em seguida, ele constrói um
novo objeto anônimo que inclui as propriedades EmployeeID, FirstName e LastName. Os resultados são então exibidos na
caixa de texto.
Agora você pode executar o aplicativo e testar como essas consultas LINQ funcionam.
393
Machine Translated by Google
Transformação XML
XSLT fornece uma abordagem declarativa baseada em regras para transformação de dados XML. No entanto, muitos
desenvolvedores acham difícil dominar o XSLT e gastam uma quantidade significativa de tempo desenvolvendo folhas de
estilo XSLT. LINQ to XML fornece um modelo de programação com o qual a maioria dos desenvolvedores está familiarizada.
Você pode transformar seu documento XML usando construção funcional, reunindo dados de várias fontes e montando-os em
um novo formulário. Isso pode reduzir os custos de desenvolvimento e manutenção. O XSLT ainda é melhor ao lidar com
cenários complexos centrados em documentos.
394
Machine Translated by Google
A classe abstrata XNode representa um nó de uma árvore XML. As classes XContainer, XText, XComment e
XProcessingInstruction herdam da classe XNode. A classe XContainer, por sua vez, atua como uma classe base para as classes
XDocument e XElement. Como você deve ter adivinhado, essas classes representam um nó de texto, comentário, instrução de
processamento, documento XML e elemento, respectivamente. As classes como XAttribute, XComment, XCData e XDeclaration são
independentes da hierarquia XNode e representam atributo, comentário, seção CDATA e declaração XML, respectivamente. A classe
XName representa o nome de um elemento (XElement) ou atributo (XAttribute). Da mesma forma, a classe XNamespace representa
um namespace de um elemento ou atributo.
Em qualquer caso, você pode carregar dados XML de um URI, um TextReader, um XmlReader ou até mesmo uma string XML.
Para demonstrar o uso de XElement no carregamento de dados XML, vamos construir um aplicativo como o mostrado na Figura 13-4.
395
Machine Translated by Google
O aplicativo consiste em quatro botões de opção para selecionar o local de onde os dados XML devem ser carregados.
A caixa de texto permite especificar um URL onde o arquivo XML está localizado ou a marcação XML bruta. Clicar no botão
Carregar carrega os dados em um XElement e exibe uma caixa de mensagem com uma mensagem de sucesso. O
manipulador de eventos Click do botão Load é mostrado na Listagem 13-8.
tentar
396
Machine Translated by Google
MessageBox.Show(ex.Message); }
O código consiste em uma série de blocos if para verificar o botão de opção selecionado. Os três primeiros blocos if
usam o método Load() da classe XElement. Observe que Load() é um método estático da classe XElement e aceita um
URI, um TextReader ou um XmlReader como fonte de dados XML. Se os dados XML estiverem na forma de uma string
bruta, o método Parse() será usado em vez de Load(). Os métodos Load() e Parse() retornam uma instância da classe
XElement que pode ser usada para processamento adicional.
ÿ Observação Para compilar o código da Listagem 13-8, você deve importar System.IO e System.Xml.
Espaços de nomes Linq . Isso se aplica à maioria dos exemplos ilustrados neste capítulo.
Para testar o aplicativo, você pode usar o mesmo arquivo Employees.xml que criamos no Capítulo 2 (consulte
Listagem 2-2 para a marcação XML completa).
os nós filhos de um elemento podem ser acessados por meio do método Nodes(). O método Nodes() retorna
uma sequência de IEnumerable<XNode> que pode ser repetida para acessar os nós individuais. Se seu interesse for
puramente em elementos, você pode usar o método Elements() que retorna uma sequência de IEnumerable<XElement>.
As propriedades FirstNode e LastNode permitem acessar o primeiro e o último nós, respectivamente. Da mesma forma,
os descendentes podem ser acessados por meio do método Descendants(). Para ver muitos desses métodos em ação,
desenvolveremos um aplicativo que preenche um controle TreeView com dados do arquivo Employees.xml. A interface do
usuário do aplicativo é mostrada na Figura 13-5.
Figura 13-5. Navegando por uma árvore XML usando LINQ to XML
397
Machine Translated by Google
O aplicativo consiste em um controle TreeView para exibir vários elementos <employee>. O TreeNode de cada
funcionário exibe ainda mais informações sobre esse funcionário. O código real para preencher a visualização em árvore vai
para o manipulador de eventos Click do botão Load Tree (consulte a Listagem 13-9).
O código primeiro carrega o arquivo Employees.xml em uma instância da classe XElement. A propriedade Nome de
a classe XElement é do tipo XName e representa o nome do elemento. A propriedade LocalName da classe XName retorna o
nome do elemento subjacente sem um qualificador de namespace. Esse nome é usado para adicionar o nó raiz do TreeView.
Como estamos interessados apenas nos elementos, o código usa o método Elements() para recuperar todos os
elementos filhos do elemento <employees>. O atributo employeeid do elemento <employee> é acessado por meio do método
Attribute(), que aceita o nome do atributo cujo valor deve ser recuperado e retorna uma instância XAttribute representando esse
atributo. A propriedade Value da classe XAttribute nos dá o valor do atributo. Um TreeNode para esse funcionário é adicionado
ao TreeView.
A propriedade booleana HasElements da classe XElement nos informa se um elemento tem algum elemento
filho. Nesse caso, iteramos por todos os descendentes do elemento <employee> atual usando seu método Descendants().
Em nosso exemplo, o método Descendants() retornará quatro XElements para os elementos <firstname>, <lastname>,
<homephone> e <notes>, respectivamente. A propriedade Value da classe XElement retorna o conteúdo interno do
elemento, que é exibido em um TreeNode.
ÿ Observação No exemplo anterior, a estrutura do documento XML era conhecida por nós. Nos casos em que o
aninhamento de tags XML não é conhecido em tempo de design, você pode usar um código recursivo que atravesse toda
a hierarquia aninhada.
398
Machine Translated by Google
Nas seções a seguir, desenvolvemos dois aplicativos: um permite pesquisar elementos com base
de seus nomes de marca e o outro permite que você pegue um funcionário específico com um ID de funcionário específico.
Recuperando elementos específicos usando o método Descendants() Quando você deseja recuperar um conjunto de
elementos com um determinado nome de tag, o método Descendants() pode ser útil. Para ilustrar o uso do método Descendants(),
desenvolvemos um aplicativo como o mostrado na Figura 13-6.
O aplicativo consiste em uma caixa de texto para especificar o nome da tag a ser pesquisado. Ao clicar no botão Pesquisar, os
resultados serão exibidos em uma caixa de listagem. Selecionar um nome de tag específico na caixa de listagem mostrará seu valor em
outra caixa de texto somente leitura. A caixa de seleção intitulada “Find Only the First Occurrence” determina se deve encontrar todas as
instâncias do nome da tag ou encerrar a pesquisa depois que a primeira correspondência for encontrada.
O código que faz o aplicativo funcionar é mostrado na Listagem 13-10.
399
Machine Translated by Google
if (!checkBox1.Checked) {
if (subset.Count() > 0) {
O código declara duas variáveis de nível de classe: root e datasource. A variável raiz é do tipo XElement e contém
um ponteiro para o nó raiz de Employees.xml. A variável datasource é uma matriz do tipo XElement e é usada para vinculação
de dados. O arquivo Employees.xml é carregado na variável raiz no evento Load do formulário.
A tarefa principal de pesquisar um nome de tag ocorre no manipulador de eventos Click do botão Pesquisar. Observe a
consulta LINQ mostrada em negrito. Ele usa o método Descendants() da classe XElement. O método Descendants() tem duas
sobrecargas: uma que não usa nenhum parâmetro e outra que usa um nome de elemento como parâmetro. A primeira sobrecarga
retorna todos os elementos descendentes do nó atual, enquanto a última sobrecarga retorna todos os elementos descendentes
que correspondem ao nome da marca especificada. Se a caixa de seleção estiver desmarcada (ou seja, queremos exibir todas as
ocorrências da tag especificada), convertemos o resultado em uma matriz de objetos XElement usando o método ToArray(). Caso
contrário, criamos um array de XElements manualmente e armazenamos nele a primeira ocorrência do elemento. Observe o uso do
método First() para recuperar apenas a primeira instância do elemento. A variável da fonte de dados é então vinculada à caixa de
listagem.
Para exibir o valor real do elemento, o código manipula o evento SelectedIndexChanged
da caixa de listagem. Dentro dele, ele simplesmente recupera o XElement selecionado do array de fonte de dados e exibe sua
propriedade Value na caixa de texto.
Para testar o aplicativo, execute-o no Visual Studio. Digite o nome na caixa de texto de pesquisa e clique no botão
Pesquisar. O aplicativo deve listar três ocorrências da tag <firstname> na caixa de listagem. Clique em qualquer uma das instâncias
e você verá seu valor exibido na outra caixa de texto.
ser pesquisados com base em valores de atributo e não apenas em nomes de tags. Os métodos Attributes() e
Attribute() ajudam você a fazer exatamente isso. Para demonstrar o uso desses métodos, desenvolveremos um aplicativo como o
mostrado na Figura 13-7.
400
Machine Translated by Google
O aplicativo consiste em uma caixa de combinação que exibe uma lista de IDs de funcionários. Lembre-se de
que o arquivo Employees.xml armazena IDs de funcionários no atributo employeeid do elemento <employee>. Ao
selecionar um ID de funcionário específico e clicar no botão Mostrar, os detalhes desse funcionário (nome, sobrenome, telefone
residencial e notas) são exibidos. O código que faz a busca é mostrado na Listagem 13-11.
401
Machine Translated by Google
O código carrega o arquivo Employees.xml em uma instância de XElement no evento Load do formulário. Em seguida,
ele chama o método Elements() no elemento raiz, fornecendo o nome da tag como funcionário. Dessa forma, são retornados
apenas os elementos com o nome da tag do funcionário. A cláusula where verifica então se esses elementos contêm o atributo
employeeid e, em caso afirmativo, retorna o valor do atributo employeeid.
O método Attributes() tem duas sobrecargas: uma não aceita parâmetros e a outra aceita o nome do atributo como
parâmetro. A primeira sobrecarga retorna todos os atributos (na forma de uma coleção XAttribute) de um elemento, enquanto a
última retorna apenas os atributos com o nome especificado. O método Attribute() aceita o nome de um atributo a ser recuperado e
retorna uma instância XAttribute associada a ele. Observe que a consulta seleciona apenas o valor do atributo. O loop foreach, em
seguida, itera pelos resultados e preenche os IDs dos funcionários na caixa de combinação.
No manipulador de eventos Click do botão Mostrar, o código busca um elemento cujo atributo employeeid
corresponde ao valor selecionado na caixa de combinação. A propriedade Value da classe XAttribute representa o valor de um
atributo. Os resultados são então exibidos nos controles Label.
O aplicativo exibe os elementos <employee> do arquivo Employees.xml. Você pode navegar entre vários funcionários
usando os controles do VCR fornecidos na parte inferior. Você também pode pular para um funcionário com um ID de funcionário
específico usando a caixa de combinação. Os botões Adicionar, Atualizar e Excluir adicionam, alteram ou removem um elemento
<employee> da árvore XML subjacente, respectivamente. O botão Salvar salva a árvore XML modificada de volta no arquivo
Employees.xml.
402
Machine Translated by Google
comboBox1.Items.Add(obj); }
FillControls(); }
O código declara uma variável do tipo XElement no nível do formulário. No evento Load, ele carrega Employees.xml nele.
Os elementos descendentes do nó raiz (ou seja, todos os elementos <employee>) são recuperados usando o método
Descendants() e seus atributos são adicionados à caixa de combinação. O método FillControls() (discutido posteriormente)
simplesmente busca o elemento <employee> correspondente ao ID do funcionário atualmente selecionado e exibe seus detalhes
(nome, sobrenome, telefone residencial e anotações) nas caixas de texto apropriadas.
parte inferior da tela permite que você navegue entre vários elementos <employee>. Os manipuladores de evento Click
dos botões próximo, anterior, primeiro e último (consulte a Listagem 13-13) ajustam essencialmente o índice selecionado da caixa
de combinação e chamam o método auxiliar FillControls() para mostrar os detalhes do funcionário selecionado.
comboBox1.SelectedIndex = 0;
FillControls(); }
comboBox1.SelectedIndex = comboBox1.SelectedIndex - 1; }
FillControls(); }
403
Machine Translated by Google
comboBox1.SelectedIndex = comboBox1.SelectedIndex + 1;
}
FillControls(); }
comboBox1.SelectedIndex = comboBox1.Items.Count - 1;
FillControls(); }
adicionar um novo funcionário, precisamos especificar o ID do funcionário, nome, sobrenome e notas nos
respectivos controles e clicar no botão Adicionar. Dentro do evento Click do botão Adicionar, precisamos criar
novas instâncias de XElement que representam os elementos <employee>, <firstname>, <lastname>, <homephone>
e <notes>. A Listagem 13-14 mostra como isso é feito.
Observe cuidadosamente a marcação na Listagem 13-14 . O código cria uma nova instância da classe
XElement usando construção funcional. O código escrito usando construção funcional fornece uma indicação visual
sobre o aninhamento da árvore XML. Depois de examinar nosso código, você pode identificar facilmente que
<firstname>, <lastname>, <homephone> e <notes> são os elementos filhos do elemento <employee>. O construtor da
classe XElement aceita uma matriz de parâmetros de elementos de conteúdo. O código fornece quatro novas instâncias
da classe XElement como o segundo parâmetro da instância principal de XElement. Como o elemento que representa
notas pode conter CDATA, seu valor é passado como uma instância da classe XCData.
O atributo employeeid do elemento <employee> recém-criado é atribuído por meio do método
SetAttributeValue(). O método SetAttributeValue() aceita um nome de atributo e seu valor como parâmetro. Se esse
atributo já existir, o método altera o valor do atributo; caso contrário, adiciona o atributo.
404
Machine Translated by Google
O código primeiro seleciona apenas o elemento <employee> cujo atributo employeeid corresponde ao selecionado
na caixa de combinação. Em seguida, ele chama o método SetElementValue() da classe XElement no funcionário
selecionado. O método SetElementValue() recebe dois parâmetros: o nome do elemento cujo valor deve ser definido e o novo
valor do elemento. Se o elemento não existir, ele será adicionado como um elemento filho do elemento atual.
um funcionário, precisamos localizá-lo nos elementos <employee> disponíveis e removê-lo da árvore XML. O manipulador
de eventos Click do botão Delete faz essa tarefa (consulte a Listagem 13-16).
O código primeiro encontra o funcionário que deve ser excluído. Em seguida, ele chama o método Remove() no
funcionário XElement. O método Remove() remove o elemento atual da árvore.
405
Machine Translated by Google
doc.Save($"{Application.StartupPath}\\Employees.xml"); }
O método Save() simplesmente aceita o caminho do arquivo onde a árvore XML será salva. Além de salvar uma
árvore XML em um arquivo, você também pode usar o método Save() para serializar uma árvore XML em objetos XmlWriter
ou TextWriter.
comboBox1.SelectedIndex = 0; } string
id do funcionário =
comboBox1.SelectedItem.ToString(); var empregados = do item em
doc.Elements() where item.Attribute("employeeid").Value == employeeid
selecionar item; foreach (var empregado em empregados) { textBox1.Text
= empregado.Element("nome").Value; textBox2.Text =
empregado.Element("sobrenome").Value; textBox3.Text = empregado.Element("telefone
residencial").Value; textBox4.Text = empregado.Element("notas").Value; } label6.Text =
$"Empregado {(comboBox1.SelectedIndex + 1)} de {comboBox1.Items.Count}"; }
406
Machine Translated by Google
ÿ Observação Além dos métodos SetElementValue() e SetAttributeValue() , você também pode usar a
propriedade Value das classes XElement e XAttribute para definir o valor de um elemento ou atributo
existente, respectivamente. Da mesma forma, além do método Add() , você pode usar os métodos
AddBeforeSelf() e AddAfterSelf() para adicionar conteúdo antes e depois do elemento atual, respectivamente.
FillControls(); }
Observe o código marcado em negrito. O código liga os manipuladores de eventos para os eventos Alterando e Alterado.
Ambos os eventos recebem um parâmetro de argumento de evento do tipo XObjectChangeEventArgs. O manipulador de
eventos para o manipulador de eventos doc_Changed é mostrado na Listagem 13-20.
407
Machine Translated by Google
}
MessageBox.Show(msg); }
A classe XObjectChangeEventArgs simplesmente fornece uma dica sobre a causa do evento. O possível
as causas são adicionar um elemento (Adicionar), remover um elemento (Remover), renomear um elemento (Nome) e
alterar o valor de um elemento (Valor). Dependendo da causa, o código pode simplesmente exibir uma mensagem para o
usuário.
Para testar esses eventos, execute o aplicativo, altere os detalhes do funcionário e clique no botão Atualizar.
Você deverá ver uma série de caixas de mensagens informando sobre as alterações. A Figura 13-9 mostra um exemplo de
execução do aplicativo.
408
Machine Translated by Google
Para ilustrar o uso das enumerações LoadOptions e SaveOptions, vamos desenvolver um aplicativo como o mostrado na
Figura 13-10.
O aplicativo carrega o arquivo Employees.xml quando você clica no botão Carregar. A caixa de seleção Preservar
espaços em branco indica se queremos preservar os espaços em branco durante o carregamento e salvamento do documento.
O manipulador de eventos Click do botão Load é mostrado na Listagem 13-21.
O código carrega o arquivo Employees.xml em uma instância da classe XElement. Dependendo do estado da caixa de
seleção, ele decide o valor LoadOptions apropriado. O valor de None indica que não pretendemos preservar espaços em branco
insignificantes, onde o valor de PreserveWhitespace indica que desejamos preservar espaços em branco. Da mesma forma, ao
salvar o documento de volta no disco, o valor SaveOptions controla o comportamento do espaço em branco. O valor de None
indica que o documento será recuado e os espaços em branco insignificantes não serão preservados, enquanto o valor de
DisableFormatting indica que não queremos recuar o documento e os espaços em branco serão preservados como estão. O
conteúdo XML carregado no XElement é exibido em uma caixa de mensagem usando o método ToString() da classe XElement.
Tente executar o aplicativo com a caixa de seleção desmarcada (consulte a Figura 13-11) e marcada (consulte
Figura 13-12). Observe que a caixa de mensagem na Figura 13-12 preservou o espaço em branco.
409
Machine Translated by Google
ÿ Nota Lembre-se de que, assim como o XML DOM, o processo de análise será afetado quando você preservar
espaços em branco (consulte a discussão do Capítulo 2 na seção “Lidando com espaços em branco”). No entanto, como
o LINQ to XML permite consultar a árvore XML de maneiras flexíveis, o espaço em branco não criará nenhum problema,
desde que você não dependa da contagem de elementos em sua lógica.
O aplicativo consiste em um botão Carregar que carrega o arquivo Employees.xml. Os detalhes do namespace são
exibidos nos controles Label. Neste exemplo, usamos o arquivo Employees.xml da Listagem 2-21 no Capítulo 2. Uma parte do
arquivo completo é fornecida na Listagem 13-22.
Várias tags de marcação desta listagem são qualificadas por namespace. O manipulador de eventos Click do botão Load
é mostrado na Listagem 13-23.
411
Machine Translated by Google
O código carrega o arquivo Employees.xml em uma instância da classe XElement. A propriedade Name de
XElement é do tipo XName. A propriedade Namespace da classe XName fornece o nome completo do namespace. O
LocalName fornece o nome dos elementos sem o qualificador de namespace, por exemplo, funcionário, enquanto chamar o
método ToString() retorna o nome do elemento junto com o qualificador de namespace. Observe como o nome do elemento
qualificado pelo namespace é mostrado na Figura 13-13. O namespace é colocado entre chaves ( { e } ) seguido do nome local
do elemento.
programaticamente, você precisa especificar as informações de namespace associadas a ela usando a classe XNamespace.
Para ilustrar como isso é feito, desenvolveremos um aplicativo como o mostrado na Figura 13-14.
Figura 13-14. Aplicativo que cria uma árvore XML com um namespace
O aplicativo consiste em quatro caixas de texto para inserir um nome de namespace, um prefixo de namespace (se
houver), um nome e um sobrenome. Clicar no botão Create XML Tree cria instâncias XElement e XAttribute conforme
necessário e exibe a árvore XML resultante em uma caixa de mensagem. O manipulador de eventos Click do botão é mostrado
na Listagem 13-24.
XNamespace ns = textBox1.Text;
XElement root = new XElement(ns + "funcionário",
new XElement(ns + "firstname",textBox3.Text), new
XElement(ns + "lastname",textBox4.Text) );
if (textBox2.Text == "")
{ root.SetAttributeValue("xmlns",
ns);
} else
MessageBox.Show(root.ToString()); }
412
Machine Translated by Google
O código cria uma instância da classe XNamespace e define o nome do namespace conforme indicado na caixa de
texto. Observe que o operador = está sobrecarregado e, portanto, você pode atribuir um valor de string diretamente à sua
instância. O elemento <employee> é então criado. Ao especificar um nome de elemento, o código anexa o nome do elemento ao
namespace. Se um prefixo de namespace não for especificado, um atributo com o nome xmlns será adicionado ao elemento raiz.
Dessa forma, o código define um namespace padrão para a árvore XML. Se um prefixo de namespace for especificado, esse prefixo
será anexado ao URI xmlns (consulte http://www.w3.org/2000/xmlns/ para obter mais informações sobre xmlns). Observe o uso da
propriedade XNamespace.Xmlns, que retorna um objeto XNamespace correspondente ao URI xmlns. Finalmente, a árvore XML
resultante é mostrada usando uma caixa de mensagem.
A Figura 13-15 mostra um exemplo de geração de uma árvore XML.
ÿ Os métodos de extensão de observação permitem adicionar métodos a uma classe existente sem herdar ou modificar
a classe original. Eles são um tipo especial de métodos estáticos que são chamados como se fossem métodos de instância
na classe estendida.
Para ilustrar o uso do método Validate(), vamos criar uma aplicação como a mostrada na
Figura 13-16.
413
Machine Translated by Google
O aplicativo consiste em duas caixas de texto e um botão. As caixas de texto aceitam um caminho para o
documento XML a ser validado e um caminho para o arquivo XML Schema. Clicar no botão Validar carrega o
documento XML em uma instância XDocument e valida o documento em relação ao esquema fornecido. Erros durante
a validação (se houver) são relatados ao usuário. Neste exemplo, usaremos o arquivo de esquema Employees.xsd
que desenvolvemos no Capítulo 5 para validar o arquivo Employees.xml. Apenas para dar uma rápida recapitulação
da estrutura do arquivo XSD Schema, a Listagem 13-25 mostra uma parte dele.
...
414
Machine Translated by Google
schema.Add(null,textBox2.Text);
manipulador ValidationEventHandler = new ValidationEventHandler(MyHandler);
doc.Validate(esquema, manipulador); }
MessageBox.Show(e.Message); }
O código carrega o documento XML de origem em uma instância da classe XDocument. Em seguida, ele cria
um XmlSchemaSet e adiciona o esquema XML especificado a ele usando o método Add(). O método de extensão Validate()
da classe XDocument aceita dois parâmetros: XmlSchemaSet e um manipulador de eventos que é chamado se a validação
falhar. O manipulador de eventos é do tipo ValidationEventHandler. O método MyHandler() atua como o manipulador de
eventos em nosso exemplo. A validação é acionada chamando o método Validate(). Se houver algum erro de validação, o
manipulador de eventos MyHandler() será invocado. O manipulador de eventos MyHandler() recebe um parâmetro do tipo
ValidationEventArgs. A propriedade Message da classe ValidationEventArgs fornece informações sobre os erros de validação.
Para testar o código, remova o atributo employeeid do primeiro elemento <employee> do arquivo Employees.xml
e execute o aplicativo. A Figura 13-17 mostra uma mensagem de erro de validação após uma execução de amostra.
ÿ Observação A forma de um documento XML refere-se aos nomes de seus elementos, nomes de atributos e aninhamento de
sua hierarquia.
•Você pode querer transformar uma árvore XML em uma árvore HTML. Da mesma forma, você pode
querer transformar uma árvore XML em outra árvore XML com uma forma totalmente diferente.
•Você pode querer projetar o código XML de origem em um tipo totalmente diferente.
Em qualquer caso, LINQ to XML, juntamente com seu recurso de construção funcional, fornece uma maneira
fácil de realizar sua tarefa.
415
Machine Translated by Google
O aplicativo consiste em um botão intitulado Transform XML to HTML e uma caixa de seleção. Ao clicar no
botão, os dados XML de Employees.xml são transformados em uma tabela HTML e salvos como Employees. htm.
Se a caixa de seleção estiver marcada, o arquivo Employees.htm será exibido no navegador.
O código que faz o trabalho de transformar os dados é mostrado na Listagem 13-27.
XElement html =
novo XElement("html",
new XElement("body",
new XElement("table",
new XAttribute("border",1), new
XElement("th", "ID do funcionário"), new
XElement("th", "First Name"), new
XElement("th", "LastName"), new
XElement("th", "Home Phone"), new
XElement("th", "Notas"),
do item na raiz.Descendants("employee") selecione
new XElement("tr", new XElement("td",
416
Machine Translated by Google
if (checkBox1.Checked) {
Process.Start($"{Application.StartupPath}\\employees.htm"); } outro {
O código carrega o arquivo Employees.xml em uma instância da classe XElement. Em seguida, ele cria outro
XElement chamado html usando construção funcional. Observe como os dados XML são selecionados dos elementos
descendentes <employee> para os novos objetos XElement. Efetivamente, em apenas uma instrução, somos capazes de
transformar a árvore XML de origem em HTML.
O novo XElement formado após a transformação é salvo no disco como o arquivo Employees.htm. Se a caixa de
seleção estiver marcada, o código abre o arquivo Employees.htm no navegador; caso contrário, uma caixa de mensagem
de sucesso é exibida. A Figura 13-19 mostra o arquivo Employees.htm gerado como resultado de um exemplo de execução
do aplicativo.
ÿ Nota Além de usar a construção funcional, você também pode criar XElements e XAttributes individuais e,
em seguida, criar o aninhamento necessário. No entanto, a construção funcional torna seu código organizado e fácil de
ler. Se seus dados XML forem gerados como resultado de algum processamento ou lógica complexa, talvez seja
necessário criar vários elementos e atributos como entidades independentes e, em seguida, associá-los uns aos outros.
417
Machine Translated by Google
Figura 13-20. Dados projetados como uma coleção e exibidos em uma grade
ÿ Observação Um tipo anônimo é um tipo que não é explicitamente definido em seu código. O nome do tipo, bem como
suas propriedades, são inferidos e gerados pelo compilador.
O manipulador de eventos Click do botão Project XML as Collection executa o trabalho real de projetar o XML e é
mostrado na Listagem 13-28.
dataGridView1.DataSource = empregados.ToArray(); }
418
Machine Translated by Google
O código carrega o arquivo Employee.xml em uma instância da classe XElement. Em seguida, seleciona o necessário
dados como um tipo anônimo com cinco propriedades, ou seja, EmployeeID, FirstName, LastName, HomePhone e
Notes. Por fim, a coleção do tipo anônimo é convertida em uma matriz para vinculação de dados com o controle
DataGridView.
Resumo
LINQ to XML fornece uma maneira totalmente nova de processar dados XML. Este capítulo examinou muitos recursos do
LINQ to XML em detalhes. Você trabalhou com classes como XElement e XDocument que carregam árvores XML para
processamento e aprendeu a lidar com espaços em branco e namespaces em LINQ to XML. Operações comuns como
análise, navegação e modificação de dados XML também foram abordadas. Você também aprendeu a validar documentos
XML em esquemas XML. Transformar dados XML usando LINQ to XML tem suas próprias vantagens e você usou
construção funcional para transformar a forma do XML de origem. Finalmente, você projetou uma árvore XML em uma
coleção.
419
Machine Translated by Google
APÊNDICE A
Criando um XmlReader e um
XmlWriter personalizados
No Capítulo 3, você aprendeu sobre as classes XmlReader e XmlWriter. As classes abstratas XmlReader e XmlWriter podem ser
usadas de três maneiras:
•Para chamar o método Create() das respectivas classes que retornam uma instância das classes genéricas
XmlReader ou XmlWriter
• Para criar classes personalizadas que herdam das classes XmlReader e XmlWriter
Você já está familiarizado com as duas primeiras abordagens. Nas seções a seguir, você aprenderá
como criar leitores e gravadores personalizados a partir das classes base abstratas XmlReader e XmlWriter.
• Ele deve aceitar a string de conexão do banco de dados e o nome da tabela para leitura.
•Os valores da coluna devem ser acessíveis especificando um índice ou nome de coluna.
Herdando de XmlReader
A classe XmlReader é uma classe abstrata e fornece várias propriedades e métodos que você precisa
substituir ao herdar dela. A Listagem A-1 mostra assinaturas dessas propriedades e métodos.
pegar; }
public abstract void Close(); public
abstract int Profundidade {
pegar; }
public abstract bool EOF {
pegar; }
string abstrata pública GetAttribute(int i); string
abstrata pública GetAttribute(nome da string, namespaceURI da string); string
abstrata pública GetAttribute(nome da string); public abstract bool TemValor {
pegar; }
public abstract bool IsEmptyElement {
pegar; }
public abstract string LocalName {
pegar; }
string abstrata pública LookupNamespace(prefixo da string);
public abstract bool MoveToAttribute(string name, string ns); public
abstract bool MoveToAttribute(string name); public abstract bool
MoveToElement(); public abstract bool MoveToFirstAttribute(); public
abstract bool MoveToNextAttribute(); public abstract XmlNameTable
NameTable {
pegar; }
string abstrata pública NamespaceURI {
pegar; }
422
Machine Translated by Google
pegar; }
string abstrata pública Prefixo {
pegar; }
public abstract bool Read(); public
abstract bool ReadAttributeValue(); resumo público
ReadState ReadState {
pegar; }
public abstract void ResolveEntity(); string
abstrata pública Valor {
pegar; }
Você pode substituir essas propriedades e métodos e escrever sua própria lógica de manipulação de dados. Se
você não deseja substituir uma propriedade ou método específico, ainda precisa ter sua implementação vazia.
Uma maneira melhor é lançar uma exceção em tais propriedades e métodos para que o chamador saiba que essas
propriedades e métodos não foram implementados por você. Não discutirei todas as propriedades aqui porque você já
está familiarizado com muitas delas (consulte o Capítulo 3 para obter mais informações).
familiarizado com a classe abstrata XmlReader, vamos criar nossa própria implementação. Para fazer isso, crie um
novo projeto de biblioteca de classe de tipo usando o Visual Studio. Adicione uma classe chamada TableReader.
Certifique-se de que as referências aos assemblies System.Xml e System.Data sejam adicionadas ao projeto. Importe
os namespaces conforme mostrado na Listagem A-2 na parte superior da classe TableReader e certifique-se de que a
classe TableReader seja herdada da classe XmlReader.
usando System.Xml;
usando System.Data;
usando System.Data.OleDb;
classe TableReader:XmlReader
{
...
Você precisa adicionar uma implementação de cada propriedade e método mencionado. O Visual Studio fornece um
atalho para adicionar implementações vazias desses membros. Vá para a linha de declaração de classe e procure o ícone
Ações rápidas na margem esquerda. Abra a lista Quick Actions e escolha a opção Implement Abstract Class (consulte a
Figura A-1).
423
Machine Translated by Google
Isso adicionará assinaturas fictícias de todas as propriedades e métodos que precisam ser substituídos.
Observe como a implementação fictícia lança uma exceção usando a palavra-chave throw. Dessa forma, se alguém
tentar usar membros não implementados, uma exceção será lançada indicando que “o método ou operação não está
implementado”. Codifique a classe TableReader conforme mostrado na Listagem A-3.
pegar
return leitor.FieldCount;
}
}
leitor.Close();
cnn.Close();
}
424
Machine Translated by Google
pegar
retornar leitor.Profundidade;
}
}
return leitor.GetValue(i).ToString();
}
return leitor.GetValue(leitor.GetOrdinal(nome)).ToString();
}
intColumnIndex = -1;
retornar verdadeiro;
}
intColumnIndex = 0;
retornar verdadeiro;
}
intColumnIndex++; if
(intColumnIndex > reader.FieldCount - 1) {
retorna falso;
}
outro {
retornar verdadeiro;
}
}
425
Machine Translated by Google
intColumnIndex = -1;
StrValue = ""; return
leitor.Read();
}
pegar
return leitor.IsDBNull(intColumnIndex);
}
}
}
outro {
retorna falso;
}
}
pegar
if (intColumnIndex == -1) {
return cmd.CommandText;
}
outro {
return leitor.GetName(intColumnIndex);
}
}
}
return strValor;
}
}
...
}
•A classe OleDbConnection é usada para estabelecer uma conexão com qualquer banco de dados
compatível com OLEDB.
•A classe OleDbCommand é usada para executar qualquer consulta, consulta SQL ou procedimentos
armazenados em um banco de dados.
A variável inteira intColumnIndex controla o índice da coluna atual cujo valor deve ser lido.
Da mesma forma, a variável string strValue armazena o valor da coluna indicada por intColumnIndex.
Inicializando as Variáveis
O construtor da classe TableReader aceita dois parâmetros: a string de conexão com o banco de dados e o nome
da tabela cujos dados serão lidos. Usando a string de conexão, o OleDbConnection é instanciado. A propriedade Connection
da classe OleDbCommand é definida como a classe OleDbConnection que acabamos de instanciar. A propriedade CommandText
da classe OleDbCommand é definida como o nome da tabela cujos dados devem ser lidos.
Dê uma olhada na propriedade CommandType. É definido como TableDirect, que retorna todas as linhas da tabela
indicadas pela propriedade CommandText. Na verdade, funciona como se tivéssemos especificado SELECT * FROM
<tableName> como a consulta. A conexão com o banco de dados é então aberta. O método ExecuteReader() de
OleDbCommand é chamado e um OleDbDataReader é recuperado.
427
Machine Translated by Google
get
{ return reader.FieldCount; }
A classe TableReader retornará valores de coluna como atributos nos dados XML resultantes. Portanto,
a propriedade somente leitura AttributeCount retorna o número total de colunas na tabela subjacente. O número
total de colunas na tabela é obtido usando a propriedade FieldCount da classe OleDbDataReader.
Fechando o Leitor
substituição pública void Close() {
leitor.Close();
cnn.Close();
pegar
retornar leitor.Profundidade;
}
}
Atributos de Leitura
string de substituição pública GetAttribute(int i) {
return leitor.GetValue(i).ToString();
}
Os valores da coluna podem ser recuperados usando duas sobrecargas do método GetAttribute(). A
primeira sobrecarga aceita o índice de atributo. No nosso caso, o índice do atributo é o mesmo que o índice
da coluna. O método GetValue() da classe OleDbDataReader aceita o índice da coluna e retorna o valor da
coluna como um objeto. O método ToString() retorna uma representação de string do objeto para o chamador.
A segunda sobrecarga aceita um nome de atributo. O método GetOrdinal() de OleDbDataReader aceita o
nome da coluna e retorna seu índice. O índice retornado é passado para o método GetValue() como antes.
428
Machine Translated by Google
intColumnIndex = -1;
retornar verdadeiro;
}
intColumnIndex = 0;
retornar verdadeiro;
}
intColumnIndex++; if
(intColumnIndex > reader.FieldCount - 1) {
retorna falso;
}
outro {
retornar verdadeiro;
}
}
•O método MoveToElement() simplesmente define o índice da coluna atual como -1, indicando que
nenhum valor de coluna pode ser lido. O método MoveToElement() destina-se a mover o leitor
para o nó do elemento a partir de qualquer um de seus atributos. Ao definir o índice da coluna
como -1, redefinimos o contador do índice da coluna e imitamos esse comportamento.
429
Machine Translated by Google
Avançando o Leitor
O método Read() permite iterar na tabela. Ele chama o método Read() da classe
OleDbDataReader e retorna um valor booleano indicando se a operação de leitura foi bem-sucedida.
À medida que o ponteiro do registro se move para um novo registro, o índice e o valor da coluna atual são redefinidos.
pegar
return leitor.IsDBNull(intColumnIndex);
}
}
A propriedade HasValue indica se o TableReader contém algum valor. Se a coluna contiver um valor
NULL, HasValue deve retornar false. O método IsDbNull() da classe OleDbDataReader aceita um índice de
coluna e retorna true se a coluna contiver um valor NULL.
Valores de leitura
}
outro {
retorna falso;
}
}
O método ReadAttributeValue() retorna o valor da coluna atual. Ele faz isso usando o método
GetValue() da classe OleDbDataReader como antes.
430
Machine Translated by Google
pegar
if (intColumnIndex == -1) {
return cmd.CommandText;
}
outro {
return leitor.GetName(intColumnIndex);
}
}
}
A propriedade Name retorna o nome da tabela subjacente ou o nome da coluna. Isso é útil para ver qual coluna
está sendo lida. O nome da tabela é obtido da propriedade CommandText da classe OleDbCommand, enquanto o nome
da coluna é obtido do método GetName() da classe OleDbDataReader.
Valores de retorno
pegar
return strValor;
}
}
Finalmente, a propriedade Value simplesmente retorna o valor armazenado na variável strValue. Note que strValue
é atribuído no método ReadAttributeValue().
As propriedades e métodos restantes não são implementados pela classe TableReader. Compile a
biblioteca de classes e você deve obter um assembly, TableReader.dll. Esse assembly pode ser usado em
aplicativos cliente para trabalhar com bancos de dados OLEDB e XML.
431
Machine Translated by Google
O aplicativo consiste em caixas de texto para inserir a string de conexão do banco de dados e o nome da
tabela, respectivamente. Depois de clicar no botão Ler, a classe TableReader é instanciada. Ele lê os dados da tabela
e os grava em um arquivo XML. O arquivo XML assim criado é exibido em um controle de navegador da Web. O
manipulador de eventos Click do botão Read contém o código mostrado na Listagem A-4.
tr.MoveToAttribute(i);
tr.ReadAttributeValue();
Writer.WriteAttributeString(tr.Name, tr.Value); }
escritor.WriteEndElement(); }
432
Machine Translated by Google
escritor.WriteEndElement();
tr.Close(); escritor.Close();
webBrowser1.Navigate($"{Application.StartupPath}\\temp.xml"); }
Antes de escrever o código anterior, adicione uma referência a TableReader.dll no aplicativo do Windows e
importe o namespace na parte superior. O código cria uma instância da classe TableReader passando a string de
conexão do banco de dados e o nome da tabela para seu construtor. Em seguida, é criado um XmlTextWriter que
grava dados em um arquivo XML temporário chamado temp.xml. A classe TableReader retornará apenas os dados
XML fragmentados; portanto, o elemento raiz é adicionado usando o método WriteStartElement() da classe
XmlTextWriter. O número total de colunas na tabela fornecida é recuperado usando a propriedade AttributeCount e
armazenado em uma variável para uso posterior.
Um loop while chama o método Read() da classe TableReader. A cada iteração, um elemento é
adicionado ao arquivo com o mesmo nome da tabela. Lembre-se de que a propriedade Name da classe TableReader
retorna o nome da tabela ou o nome da coluna, dependendo do índice da coluna atual. Como acabamos de chamar
o método Read(), o índice da coluna será -1 e, portanto, o nome da tabela será retornado.
Em seguida, um loop for itera todos os atributos - isto é, colunas. Com cada iteração do loop for, o valor do
atributo é lido usando o método ReadAttributeValue(). Um atributo é então gravado no arquivo junto com seu valor
usando o método WriteAttributeString() da classe XmlTextWriter. O método WriteEndElement() da classe
XmlTextWriter grava tags finais para o elemento aberto mais próximo. O TableReader e o XmlTextReader são
fechados usando seus respectivos métodos Close(). Por fim, o método Navigate() do controle do navegador da Web
mostra ao usuário o arquivo XML.
<rss versão="2.0">
<canal>
<title>Meu Web Site</title>
<link>http://localhost/mywebsite</link>
<description>Últimos Artigos de Meu Web Site</description>
<copyright>Copyright (C) Meu Web Site. Todos os direitos reservados.</copyright>
<generator>Meu gerador de RSS</generator> <item>
</canal>
</rss>
433
Machine Translated by Google
•<rss> forma a marca raiz e possui um atributo de versão. O atributo version especifica a versão
da especificação RSS usada pela marcação, 2.0 neste caso.
•<canal> contém tags como nós <título>, <link> e <item>. Um canal representa informações
de metadados de uma fonte específica. Ele atua essencialmente como um contêiner para
o restante das tags. Um documento RSS pode conter um ou mais canais.
Além das tags anteriores, pode haver uma ou mais tags <item>, cada uma representando um item real que você
deseja compartilhar (por exemplo, um artigo ou uma entrada de blog). Cada tag <item> contém ainda os seguintes
subnós:
ÿ Nota A marcação RSS mostrada aqui é a marcação básica. Pode ser necessário adicionar tags adicionais para
incorporar informações adicionais. Você pode obter mais informações sobre RSS em https://en.wikipedia.org/wiki/RSS.
O .NET Framework fornece o namespace System.ServiceModel.Syndication, que contém classes para ler e
gravar um feed RSS. No entanto, como exemplo, criaremos nossa própria classe personalizada chamada RssWriter.
A classe RssWriter herdará de XmlWriter e permitirá que você emita feeds RSS facilmente.
Para criar o RssWriter, você precisa criar um projeto de biblioteca de classes. Como antes, certifique-se de adicionar uma
referência ao assembly System.Xml.
implementação personalizada de XmlWriter, você precisa herdar dele e substituir as propriedades e métodos
mostrados na Listagem A-6.
434
Machine Translated by Google
WriteDocType(string name, string pubid, string sysid, string subset); public abstract
void WriteEndAttribute(); public abstract void WriteEndDocument(); public abstract void
WriteEndElement(); public abstract void WriteEntityRef(string name); public abstract void
WriteFullEndElement(); public abstract void WriteProcessingInstruction(nome da string,
texto da string); public abstract void WriteRaw(string data); public abstract void
WriteRaw(char[] buffer, int index, int count); público abstrato vazio
pegar; }
public abstract void WriteString(string texto); public
abstract void WriteSurrogateCharEntity(char lowChar, char highChar); public abstract void
WriteWhitespace(string ws);
Muitas dessas propriedades e métodos devem ser familiares para você porque os discutimos no Capítulo 3.
objStream = fluxo;
escritor = XmlWriter.Create(objStream); }
O código declara variáveis de nível de classe dos tipos XmlWriter e Stream, respectivamente. O construtor
recebe um parâmetro do tipo Stream. Este fluxo atua como um fluxo de saída para emitir os feeds RSS.
Uma instância do XmlWriter é construída usando o método Create() da classe XmlWriter. O fluxo passado
para o construtor é fornecido ao método Create() para que a instância recém-criada de XmlWriter grave nesse
fluxo.
435
Machine Translated by Google
O fluxo precisa ser fechado e liberado para garantir que os dados emitidos sejam salvos corretamente. Os dois
métodos substituídos — Close() e Flush() — fazem exatamente isso. A Listagem A-8 mostra esses métodos.
O método Close() chama o método Close() do fluxo subjacente, bem como o do XmlWriter. Da mesma
forma, o método Flush() chama o método Flush() do XmlWriter para que os dados sejam liberados para o fluxo.
Seria bom fornecer prontamente nomes de tags e atributos RSS para que você não precise se lembrar deles. Isso é obtido
criando duas enumerações: RssElements e RssAttributes. As enumerações são mostradas na Listagem A-9.
Rss,Canal,Título,Descrição,Link,Copyright,Gerador,Item,PubData }
Versão
}
A enumeração RssElements contém valores para representar elementos RSS. Os Atributos RSS
enumeration contém apenas um valor — Version — que representa o atributo version do elemento <rss>.
Escrevendo elementos
Para emitir o feed RSS, você precisa escrever elementos como <rss> e <item> no fluxo de saída. Para
isso, criaremos três métodos: WriteElement(), WriteElementString() e WriteEndElement().
O código completo desses métodos é mostrado na Listagem A-10.
436
Machine Translated by Google
caso RssElements.Channel:
elementName = "canal"; quebrar;
case RssElements.Copyright:
elementName = "copyright"; quebrar;
case RssElements.Description:
elementName = "descrição";
quebrar; caso RssElements.Generator:
nomeDoElemento = "item";
quebrar; caso
RssElements.Link:
elementName = "link";
quebrar; caso
RssElements.PubDate:
elementName = "pubDate";
quebrar; caso RssElementos.Rss:
nomeDoElemento = "rss";
quebrar; caso
RssElements.Title:
elementName = "título";
quebrar; }
escritor.WriteStartElement(elementName); }
caso RssElements.Channel:
elementName = "canal"; quebrar;
case RssElements.Copyright:
elementName = "copyright"; quebrar;
case RssElements.Description:
elementName = "descrição";
quebrar;
437
Machine Translated by Google
caso RssElements.Generator:
elementName = "gerador"; quebrar;
caso RssElements.Item:
nomeDoElemento = "item";
quebrar; caso
RssElements.Link:
elementName = "link";
quebrar; caso
RssElements.PubDate:
elementName = "pubDate";
quebrar; caso RssElementos.Rss:
nomeDoElemento = "rss";
quebrar; caso
RssElements.Title:
elementName = "título";
quebrar;
} escritor.WriteElementString(elementName, valor); }
escritor.WriteEndElement();
}
O método WriteElementString() aceita dois parâmetros: RssElements e o valor do elemento. Ele contém uma
instrução switch semelhante à do método anterior e armazena o nome do elemento em uma variável. O método
WriteElementString() da classe XmlWriter é chamado passando o nome do elemento e seu valor. Observe que
WriteStartElement() e WriteElementString() são métodos novos, ou seja, não são definidos pela classe base XmlWriter.
Atributos de escrita
Assim como adicionamos métodos para escrever elementos, também precisamos adicionar métodos para emitir atributos.
Três métodos—WriteStartAttribute(), WriteAttributeString() e WriteEndAttribute()—farão esse trabalho.
A Listagem A-11 mostra esses métodos.
438
Machine Translated by Google
O método WriteStartAttribute() aceita um parâmetro do tipo RssAttributes. Dentro dele verifica se o atributo
a ser emitido é Version, e se for, chama o método WriteStartAttribute() da instância XmlWriter para escrever o
atributo.
O método WriteAttributeString() aceita dois parâmetros: RssAttributes e o valor do
atributo. Em seguida, ele chama o método WriteAttributeString() da instância XmlWriter passando o valor e a
versão fornecidos como o nome do atributo.
O método WriteEndAttribute() simplesmente chama o método WriteEndAttribute() da instância XmlWriter.
Dados de gravação
Embora os métodos que criamos para escrever elementos cuidem da maior parte da geração do feed RSS, você pode precisar de
métodos adicionais para emitir comentários, dados de caracteres, espaços em branco e assim por diante. Para realizar essa tarefa,
escreveremos um conjunto de métodos, conforme mostrado na Listagem A-12.
439
Machine Translated by Google
escritor.WriteComentário(texto); }
escritor.WriteWhitespace(ws); }
escritor.WriteString(texto); }
Esses métodos não contêm muito código. Eles simplesmente chamam o método correspondente na instância
XmlWriter. Por exemplo, o método WriteCData() aceita uma string e chama o método WriteCData() do XmlWriter passando
a string. Os métodos WriteChars(), WriteComment(), WriteWhitespace() e WriteString() também chamam os respectivos
métodos da ocorrência de XmlWriter.
O método WriteStartDocument() tem duas sobrecargas. Aquele com um parâmetro booleano emite um atributo
autônomo. Ambos os métodos chamam as respectivas sobrecargas do método WriteStartDocument() na instância de XmlWriter.
O método WriteEndDocument() simplesmente chama o método WriteEndDocument() da instância XmlWriter.
É isso: a classe RssWriter está pronta. Compile a biblioteca de classes para obter seu assembly de saída.
440
Machine Translated by Google
Response.ContentEncoding = System.Text.Encoding.UTF8;
Response.ContentType = "texto/xml"; Gravador RssWriter = new
RssWriter(Response.OutputStream); escritor.WriteStartElement(RssElements.Rss);
Writer.WriteAttributeString(RssAttributes.Version, "2.0");
escritor.WriteStartElement(RssElements.Channel);
Writer.WriteElementString(RssElements.Title, "Meu Web Site");
Writer.WriteElementString(RssElements.Link, "http://localhost/mywebsite");
Writer.WriteElementString(RssElements.Description, "Últimos artigos do meu site");
Writer.WriteElementString(RssElements.Copyright, "Copyright (C) Meu Web Site. Todos os direitos reservados.");
Writer.WriteElementString(RssElements.Generator, "Meu XML RSS Generator");
escritor.WriteStartElement(RssElements.Item); escritor.WriteElementString(RssElements.Title, "
O código define a propriedade ContentEncoding do objeto Response como UTF-8 (ou seja, ASCII). Ele também define
a propriedade ContentType para text/xml. Dessa forma, o navegador sabe que a resposta são dados XML em vez de
HTML. Uma nova instância da classe RssWriter é então criada. O OutputStream do objeto Response é passado como um
parâmetro para o construtor da classe RssWriter. Dessa forma, os dados XML serão gravados diretamente no fluxo de
resposta.
Em seguida, uma a uma, as tags RSS são emitidas para gerar um feed RSS, conforme mostrado na
Listagem A-5 anterior. Observe como a enumeração RssElements facilitou nosso trabalho. Vários métodos, como
WriteElementString() e WriteStartElement(), fazem uso extensivo da enumeração RssElements.
Após a gravação do feed, a instância RssWriter é fechada. Por fim, o método End() do objeto Response é chamado para
que o fluxo de resposta seja liberado para o cliente.
ÿ Observação Para simplificar, o código emite valores embutidos em código. Na maioria dos casos do mundo real, você
recuperará dados como título, URL e data de publicação de uma tabela de banco de dados.
441
Machine Translated by Google
Se você executar o formulário da web depois de escrever o código, ele deve ser semelhante à Figura A-3.
Como você pode ver, o Firefox identificou a resposta como um feed RSS válido e exibido de uma maneira especial para facilitar a
leitura. Se você visualizar o Page Source da página, verá a marcação XML bruta, conforme mostrado na Figura A-4.
Esta é a mesma marcação de feed RSS que você emitiu a partir do code-behind do formulário da web.
Resumo
Neste apêndice, você aprendeu a criar implementações personalizadas das classes XmlReader e XmlWriter.
As classes XmlReader e XmlWriter são classes abstratas. Para criar leitores e gravadores personalizados, você precisa herdar deles e
substituir várias propriedades e métodos. Dessa forma, você pode estender facilmente a funcionalidade pronta para uso exposta por essas
classes para cenários específicos.
442
Machine Translated by Google
APÊNDICE B
Recursos
Os recursos a seguir ajudarão você a aprender mais sobre XML, .NET Framework e tecnologias aliadas.
Referência System.Xml
https://msdn.microsoft.com/library/system.xml.aspx
Apêndice B ÿ Recursos
.NET/ASP.NET/WCF/API da Web
https://www.microsoft.com/net
https://www.asp.net https://
docs.microsoft.com/en-us/dotnet/framework/wcf https://
docs.microsoft.com/en -us/aspnet/web-api
Wikipédia—Seção XML
http://en.wikipedia.org/wiki/XML
Programação SQLXML
https://docs.microsoft.com/en-us/sql/relational-databases/sqlxml/sqlxml-4-0-programming concept
444
Machine Translated by Google
Índice
B
Pão ralado, 361
Aplicativos business-to-business (B2B), 9 método
button1_Click(), 340
ÿ ÍNDICE
446
Machine Translated by Google
ÿ ÍNDICE
F
Método auxiliar FillControls(), 403, 406
Método FillControls(), 50, 403
Definição do tipo de documento (DTD), 112 Método FillEmployees(), 187, 190
analisadores baseados em DOM, 8 Primogênito, 35
Construção funcional, 394
ÿÿÿÿÿÿÿE
447
Machine Translated by Google
ÿ ÍNDICE
Projeção de transformação de
Eu, J, K árvore XML, cenários 418–
Opção InferSchema, 200 419 , forma 415 , 416–417
Método InferXmlSchema(), 203–204
DTD embutido, 136 ÚltimoFilho, 35
Esquema embutido, 137 Caminho de localização, 86
Inserir () método da web, 251
M
eu
Classe de gerente, 221, 223
Arquitetura Language Integrated Query Controle de menu, 357–358
(LINQ), 387–388 valores de atributo, Menu Editor de DataBindings, 357
400–402 seleção de dados, 393 método Método MoveToAttribute(), 69
Descendants(), 399–400 classe Método MoveToElement(), 69
Employee, 389–390 dados de filtragem, Método MoveToFirstAttribute(), 69
392–393 método GetEmployees(), 390 Método MoveToNextAttribute(), 69
dados de agrupamento, 390–391 Método MyHandler(), 415
manipulação de namespace, 394
namespaces, 412–413 operação de
classificação, 392 operadores de consulta N
padrão, 388 métodos estáticos, 394 Tabela de nomes,
criação de método Validate(), 413 65 .NET Core,
Employees.xsd, 414 mensagem de erro, 12 .NET Framework
415 arquivo XSD Schema, 414 construção Base Class Library, 12
visual, 394 Padrões W3C, 394 espaços definições, 11 vs. .NET
em branco, 394 enumerações Core, 12 Stack of, 11 XML
LoadOptions e SaveOptions, 409 ADO.NET, 17 ASP.NET
LoadOptions.None, 410 Web Forms, 17–18
LoadOptions.Preserve Whitespace, assemblies e
410 hierarquia de classe XML, 395 namespaces, 13–14 modelo clássico
modificação de dados XML de análise XML, 14 arquivos de configuração,
15–17 documentação, 19–21 modelo de
análise baseado em LINQ, 14 serialização,
18 SQL Server, 21 marcação XAML, 21
método NewEmployeesRow(), 210 método
NewRow(), 188 NextSibling, 35 métodos
Nodes(), 397 testes Node, 86 analisadores
não validadores, 9
448
Machine Translated by Google
ÿ ÍNDICE
449
Machine Translated by Google
ÿ ÍNDICE
serviços web
EM ASMX, 242
Cláusula UNION ALL, 311 métodos de chamada da web
Método UpdateLabel(), 50 Excluir () método da web, 259 lista
Método web Update(), 252 suspensa, 257
Inserir () método da web, 258
Método SelectAll(), 258
EM
Método da web SelectByID(), 258
Validando analisadores, 9 Método web Update(), 259
Documentos válidos, 7 componente, 239
Estúdio visual
Funcionalidade CRUD
criações de bibliotecas de classes, 26–28 abordagem de código primeiro,
Designer, 337 249 informações de string de conexão
Classe MessageBox, 25 de banco de dados, 249
execuções de amostra, 25 Excluir () método da web, 252
Visual Studio 2017, 21 Classe de Entidade Empregada, 250
Aplicativo do Windows, Visual Studio IDE, 23 Mesa de empregados, 250
Formulário do Windows, controle de botão, 23 Entity Framework, 247–248
Aplicativo de formulários do Windows, 22 Inserir () método da web, 251
Classe de contexto Northwind, 250
Método web SelectAll(), 251
EM Método da web SelectByID(), 251, 253
Site W3C, 443 páginas Método web Update(), 252
de erro personalizadas Propriedade Description, 247
do arquivo Web.config, Arquivo EmployeeManager.asmx.cs, 242 Modelo
páginas administrativas, 376 de projeto vazio, 241 Método HelloWorld(), 246
manipulador de eventos Click, 376 página de ajuda, 243 Botão invocar, 245
Default.aspx, 376 HttpException, Propriedade Namespace, 243 proxy, 240, 254–
377 atributo de modo, 377 256 cabeçalhos SOAP, 260–263 solicitação, 245,
especificação, 377 atributo 259 resposta, 246 serialização XML, 264–265
statusCode, 377 atributo statusCode, parâmetro de string, 247 no Visual Studio, 241
378 organização de formulários da diretiva @WebService, 242 WSDL, 244 ligação,
web, 376 funcionário listagem, 370– 267 elementos, 266 serviço web
371 Autenticação de formulário EmployeeManager, 265–266 tipos de porta,
267 modelo de solicitação e resposta , 266
definições de tipo, 266 organização WS-I,
Authenticate event handler, 372–373 243
<authentication>, 372 Click event handler, 373
Default.aspx e Login.aspx, 372, 373 defaultUrl
attribute, 372 login page, 374 loginUrl attribute, 372
Page_Load event handler, 373 role-based security,
371 Método SignOut(), 374 atributos de usuários,
372
450
Machine Translated by Google
ÿ ÍNDICE
451
Machine Translated by Google
ÿ ÍNDICE
452
Machine Translated by Google
ÿ ÍNDICE
453