Panel.DefaultButton

Agora dentro do controle Panel, temos uma propriedade chamada DefaultButton, que recebe uma string com o nome do controle (Button) que será “clicado” quando a tecla ENTER for pressionada pelo usuário dentro do Panel respectivo. O exemplo é algo como:

     <asp:Panel ID=”Panel1″ … DefaultButton=”Button1″></asp:Panel>
     <asp:Panel ID=”Panel2″ … DefaultButton=”Button2″></asp:Panel>

Eis uma boa “feature”, já que nas versões anteriores, tínhamos que utilizar Javascript.

KeyField no GridView

Na versão 1.x do .NET Framework/ASP.NET, temos no DataGrid, a propriedade DataKeyField. Esta é utilizada para definirmos um campo da nossa fonte de dados (DataSource) que identificará como único aquele item/registro. Na maior parte das vezes é representado pelo campo Primary Key da Tabela.

Bastava definir uma coluna (uma única coluna/campo) na propriedade DataKeyField, e já conseguimos resgatar o índice deste item da seguinte forma:

     Dim userID As Integer = Convert.ToInt32(Me.dgUsers.DataKeys(N))
     Response.Write(userID)

Onde N é um número inteiro que representa a linha qual voce deseja recuperar o Key Field.

Agora no ASP.NET 2.0, no controle GridView, temos ainda essa propriedade, mas com um nome um pouco diferente: DataKeyNames. Como podemos reparar, ela não permite só um campo, mas sim, um array de strings, contendo o nome das colunas/campos que desejar definir para poder identificar o registro. Os campos são separados por vírgula (“,”), exemplo:

     <asp:GridView …. DataKeyNames=”ID, AnotherID”></asp:GridView>

E para recuperar, resgatamos uma coleção de “chave-valor” que nos é retornado através da propriedade Values do DataKey:

     Dim keys As DataKey = Me.GridView1.DataKeys(N)
     For Each d As DictionaryEntry In keys.Values
          Response.Write(d.Key.ToString() & “: ” & d.Value.ToString() & “<BR>”)
     Next

Onde N é um número inteiro que representa a linha qual voce deseja recuperar o DataKey.

Apesar desta funcionalidade, eu particularmente nunca precisei ter mais de um campo para identificar a linha do meu controle.

Restringindo Arquivos – FormsAuthentication

Hoje estive que disponibilizar em um área restrita da empresa que trabalho um arquivo PDF para que os usuários que tem acesso a esta área do website possam baixá-lo. Criei um subdiretório dentro da pasta que está restringinda no arquivo Web.Config. Até aí estava tudo certo, até quando tentei chamar o arquivo PDF diretamente na URL do browser.

Imaginava eu, que pelo fato de estar dentro do diretório que é restrito o mesmo também seria e me enganei. Antes de ir mais além, eu criei uma classe/handler onde a mesma implementava a interface IHttpHandler e ali verificava se o usuário estava ou não autenticado. Em seguida fiz as entradas no arquivo Web.Config incluindo os elementos de httpHandlers, mapeando assim o arquivo PDF.

Da mesma forma o problema ainda persistia e eu continuava sem entender. Dando uma palavra com o meu amigo João, ele me deu uma luz, onde disse que como arquivos com extensões *.pdf não passam pelo ASP.NET, ou melhor, não estão mapeados dentro do IIS, logo, não seria possível validar a autenticação antes de exibir o arquivo ao usuário.

Resumindo, o que tive que fazer foi apenas fazer o mapping para arquivos PDF dentro do IIS para que o filtro ISAPI (aspnet_isapi.dll) passe a fazer as verificações dos mesmos antes de exibí-los. Como associamos o ASP.NET à arquivos PDFs, isso já é o necessário. Não é mais necessário a criação de um Handler específico e fazer as entradas de mappings dentro do arquivo Web.Config, que como já disse acima, se não estiver associado ao ASP.NET, não irá adiantar nada. Se lermos a documentação temos:

Please note that Microsoft Internet Information Services (IIS) has its own concept of mapping extensions to ISAPIs. For the settings for a given extension in this section to take effect, the extension must be mapped in IIS to ASP.NET ISAPI. For nonstandard extensions (something other than .aspx, .asmx, .asax, and so on), the user must configure IIS.

Pois bem, abaixo a imagem ilustra como fazermos para criar uma nova entrada (mapping) dentro do IIS para que o ASP.NET passe também a gerí-los:

Para chegar até esta tela, clique com o botão direito em cima do site dentro do IIS, vá até Propriedades, depois em Home Directory, em seguida em Configuration e finalmente clique em Add para fazer um novo mapping. Este processo deve ser igual para todos os arquivos que desejar fazer o mesmo.

Thread was being aborted.

É bastante comum vermos o seguinte erro “Thread was being aborted.” em fórums, listas e aplicações que desenvolvemos. Há vezes em que o método Response.Redirect(“url”) é chamado dentro de um código gerenciado pelo Try…Catch.

O grande problema é que o método Response.Redirect(“url”) quando chamado desta forma, internamente ele invoca o método Response.End, e este por sua vez dispara a seguinte Exception: ThreadAbortException para finalizar o processamento da página corrente. Sendo assim, se o Response.Redirect estiver sendo gerenciado pelo bloco Try, ele capturará a Exception, e assim, se existir o bloco Catch, será executado.

Como muitos não conhecem, existe um overload do método Response.Redirect(“url”, “True | False”) que além de receber a URL qual o usuário será redirecionado, recebe também um parâmetro booleano que indica se a página terá ou não o seu processamento abortado através do método Response.End. Se definirmos como False, a página é executada até o término e assim, não invocando o método Response.End, e assim o usuário é redirecionado para a nova página.

Abaixo o código interno do método Response.Redirect:

Public Sub Redirect(ByVal url As String, ByVal endResponse As Boolean)
      ‘….
      If endResponse Then
            Me.End
      End If
End Sub

E quando não informarmos o parâmetro booleano, por default é informado como True, logo, a página será finalizada através do Response.End e assim vai atirar a Exception.

Contentores de Dados em ASP.NET

Bem, qual a melhor forma de transportar dados em uma aplicação Web? Se estivesse trabalhando em uma aplicação Windows Forms, acho interessante o uso do Dataset, visto que muitas vezes a conexão com a internet ou mesmo com o servidor de DB não é contínua, e sendo assim, não é possível a cada dado que precisarmos irmos até este servidor, buscar o que necessitamos. É neste cenário que olho com bons olhos para o Dataset, onde recuperamos os dados de uma DB qualquer, armazenamos neste Dataset, fechando a conexão com o servidor de DB e trabalhamos com esse Dataset, desconectado!

Uma vez que finalizamos o trabalho neste dados desconectados, reabrimos a conexão com a DB e persistimos esses na Base de Dados e pronto, tudo já estará lá atualizado.

Vale lembrar que quando estou me referindo à Windows Forms, estou querendo abranger sistemas para Windows – Desktop, ou mesmo aplicações para Pockets. Mas agora, e em uma aplicação Web? Será que vale mesmo a pena utilizar Datasets em uma aplicação ASP.NET? Na minha opinião, não acho isso lá muito interessante.

Se para acessar e trabalhar em uma aplicação ASP.NET/Web, tem que estar 100% OnLine, então vejo que não há a necessidade de utilizar o Dataset para recuperar e trabalhar com esses dados “localmente”. Já sabemos que o uso do DataReader é muitas vezes mais rápido que o Dataset (isso evita colocar aqui PPT’s que confirmem isso :P), e é uma ótima opção. Quem realmente defende Datasets, condenam os DataReaders por eles serem somente “foward-only” e “read-only”, que somente com essas características não é possível trabalhar com os dados “desconectadamente”.

Fiz todos os cursos da carreira MCSD.NET, e vi exaustivamente Datasets nestes cursos (que pelo fato de não usá-los, não me recordo), inclusive no curso de ASP.NET, ou seja, a Microsoft também “prega” o uso deles. Se o mesmo demora muito à carregar, então porque não se utiliza DataReader para recuperar e assim exibir os dados? Talvez o conceito de Datasets não está muito bem definido para o pessoal, visto que vejo códigos do tipo:

          Private Sub Page_Load(…)
               If Not Page.IsPostBack() Then
                    CarregaDS()
               End If
          End Sub

          Private Sub CarregaDS()
               MeuDataAdapter.Fill(MeuDataSet)
               MeuDataGrid.DataSource = MeuDataSet
               MeuDataGrid.DataBind()
          End Sub

          Private Sub DataGrid1_PageIndexChanged(….)
               MeuDataGrid.CurrentPageIndex = e.NewPageIndex
               CarregaDS()
          End Sub

Como podem ver neste caso, estamos usando um objeto extremamente pesado para carregar os dados da DB (isso sem contar quando o Dataset não é armazenado em Session). Para isso, ao invés de utilizar Dataset, porque não utilizam DataReader, recuperando da DB apenas os registros que são necessários para a construção e exibição da página (me refiro à paginação do DataGrid) requerida pela usuário?

Sem contar que, se criar uma aplicação realmente orientada à objetos, deve ficar terrivelmente complicado preencher e popular os objetos da aplicação, objetos quais deixam a aplicação mais próxima do mundo real. E aqui entraria o OR/M, mas deixa isso para um outro post/artigo.

Pois bem, na minha opinião, não acho o uso de Datasets viável em uma aplicação Web, ou melhor, talvez há alguma utilidade sim, como por exemplo para armazenar um retorno de Web Services, mas fora isso, acho que o DataReader muito mais ideal, mesmo sendo somente leitura.

Update:
Com relação ao CRUD na DB, claro que não utilizo o Dataset para isso, ou seja, executo os comandos diretamente na DB. Se for alguma aplicação (claro, ASP.NET) simples, utilizo uma DAL, mas se for algo mais complexo, o ideal é modelar calmamente, e assim criar aos DAOs que serão responsáveis pela persistência dos objetos na DB. Viva o OR/M 🙂

Javascript e ASP.NET

Nunca fui la muito fã de JavaScript, mas estou aos poucos vendo a sua GRANDE utilidade em aplicativos web-Based. Estou trabalhando em um projeto onde a interação com o usuário é alta, ou seja, formulários complexos com grandes funcionalidades e também regras que devem ser validadas client-side.

Com este cenário, talvez conseguiria fazer isso utilizando VB.NET ou C#, mas com a desvantagem de que este código ir correr no servidor, sendo necessário fazer o PostBack da página, executando a rotina e devolvendo para o cliente o que realmente lhe interessa. Isso pode tornar custoso, levando em consideração um grande volume de acessos simultaneos. Nesse caso, JavaScript está dando um banho de praticidade para o cliente final, tornando as coisas bem interativas, sem a necessidades dos PostBacks.

O problema que quando o código começa a ficar um pouco grande, começa a ficar complicado, mas nada tão impossível de se mexer. Uma saída intessante para um caso que tive vou utilizar Remote Scripting, pois quando o usuário digita o código (CNPJ) do Cliente em um TextBox, preciso complementar o resto do formulário (Endereço, Cidade, Estado e CEP) referentes a este Cliente que devem vir da DB (SQL Server). Poderia sem problemas utilizar o evento TextChanged (server-side) do TextBox, mas isso me obrigaria um PostBack, tendo que submeter a página ao servidor e asssim tendo que construí-la novamente no cliente com os dados necessários.

Já com o uso do Remote Scripting, as coisas ficam mais transparentes, ou seja, é invocado um determinado método assíncrono, e o resultado é devolvido através de uma funçao CallBack (em JavaScript) que por sua vez (no meu caso preenche os controles) faz o que quiser com o resultado.

Que é trabalhoso, não tenho dúvida, mas deixa a aplicação bastante interessante, principalmente para os usuários que a utilizarão.

A única pergunta que gostaria de deixar no ar é a seguinte: Já estou fazendo uma porção de validações no cliente, principalmente com relação a datas, onde existem regras para que a mesma seja digitada apenas em um determinado período, verificações de caracteres válidos, entre várias outras verificações. A questão é: depois de tantas verificações, validações client-side será que ainda é necessário fazer essas mesmas validações server-side?

CallBacks em ServerControls (ASP.NET 2.0)

O ASP.NET 2.0 nos trará a possibilidade de utilizamos CallBacks em nossos WebForms. O que isso representa:

Há muitos momentos, em que precisamos resgatar algum valor, ou mesmo ir até a DB, buscar algum dado para comparação, validação, etc. e sempre temos que atualizar a página para recuperarmos o valor, qual muitas vezes, precisamos apenas mudar poucas coisas em seu layout, ou seja, sendo desnecessário a reconstrução de toda a página novamente (que é o que acontece atualmente com PostBacks).

Tendo este cenário, entra em cena os ServerControls que suportarão Async CallBacks. Com isso, poderemos ir até o servidor, fazermos comparações, validações, etc., sem termos que reconstruir novamente todo o WebForm.

Atualmente é possível fazer isso com a utilização de XMLHTTP, mas o problema é que temos que escrever muito código JavaScript, tanto para fazer a requisição remota, quanto para manipular os objetos da página. Veremos abaixos os dois casos (PostBacks vs. CallBacks):

PostBack
Init
Load State
Process PostBack Data
Load
PostBack Events
Save State
PreRender
Render
Unload
 
CallBack
Init
Load State
Process PostBack Data
Load
CallBack Event
Unload

Como podemos ver em CallBacks, os eventos PreRender e Render não são invocados (eventos, quais são responsáveis pela construção do HTML, que posteriormente é enviado ao browser pelo Objeto Response), pois como dito anteriormente, não é necessário passarmos por essa fase, já que os WebForms estão “prontos”.

Evento ItemDataBound Data[Grid][List]

Muitas vezes necessitamos resgatar os valores que estão sendo inseridos no Data[Grid][List] para manipulações ou até mesmo efetuar verificações para exibir uma informação mais personalizada ou ocultar algum controle, etc. Tendo este cenário, uma solução seria utilizar o evento ItemDataBound destes controles. Este evento é disparado sempre quando uma nova linha é inserida no controle, durante o DataBind().

Mas para isso, algumas condições devem ser verificadas para que não ocorra um erro. A começar que devemos verificar o tipo da linha que esta sendo inserida no controle através do Enumerador ListItemType. Os dados estão sempre nas linhas do tipo Item ou AlternatingItem, para certificarmos que não seja estejamos inserindo ou tentando acessar um controle que não exista, mesmo porque muitas vezes não utilizamos as linhas do controle do tipo Header e Footer para inserir os dados da DB e sim para uma somatória, ou algo assim, que no caso do DataBind() devem ser desconsideradas.

Então, o código ficará desta forma:

[ VB.NET ]
    Private Sub DataGrid1_ItemDataBound(ByVal sender As Object, _
        ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles DataGrid1.ItemDataBound
        If e.Item.ItemType = ListItemType.AlternatingItem OrElse e.Item.ItemType = ListItemType.Item Then
            ‘…
        End If
    End Sub

[ C# ]
  private void DataGrid1_ItemDataBound(object sender, 
      System.Web.UI.WebControls.DataGridItemEventArgs e)
   {   
       if (e.Item.ItemType == ListItemType.AlternatingItem || e.Item.ItemType == ListItemType.Item)
          {
               //…
          }
   }

Feito essa verificação, devemos agora nos atentar para qual o tipo de fonte de dados está sendo passado para a propriedade DataSource do controle. Isso varia quando utilizamos DataSets e DataReaders, pois devemos fazer o Cast para o tipo de objeto correto. Vejamos abaixo as duas opções:

[ VB.NET ] [ DataSets ]
DirectCast(e.Item.DataItem, DataRowView).Item(“NomeColuna”)
DirectCast(e.Item.DataItem, DataRowView).Item(0)

[ C# ] [ DataSets ]
((DataRowView)e.Item.DataItem)[“NomeColuna”]
((DataRowView)e.Item.DataItem)[0]

[ VB.NET ] [ DataReaders ]
DirectCast(e.Item.DataItem, DbDataRecord).Item(“NomeColuna”)
DirectCast(e.Item.DataItem, DbDataRecord).Item(0)

[ C# ] [ DataReaders ]
((DbDataRecord)e.Item.DataItem)[“NomeColuna”]
((DbDataRecord)e.Item.DataItem)[0]

Lembrando que no caso do uso dos DataReaders, devemos importar o Namespace System.Data.Common para podermos utilizar a Classe DbDataRecord.

Com isso, já podemos ter acesso aos dados/colunas que estão sendo adicionados no nosso controle, e com isso, nos permite fazermos manipulações e até mesmo testarmos condicionais para exibir ou não uma mensagem mais personalizada para o usuário de acordo com esses dados vindos da DB.

Escrevendo em Imagens

Há momentos em que precisamos por algum motivo escrevermos em uma imagem (até hoje não entendi o porque disso, se alguém puder explicar, agradeço) para que as pessoas confirmem, escrevendo a mesma String em uma caixa de texto. Apenas com algumas linhas de código, é bem simples fazer isso em .NET. A seguir os passos:

1 – No seu WebForm1.aspx, coloque um controle do tipo Image. Crie mais um WebForm em sua aplicação chamado WebForm2.aspx. Atribua à propriedade ImageUrl do controle Image que está no WebForm1.aspx para WebForm2.aspx

2 – No evento Load do WebForm2, faça:

Dim imagem As New Bitmap(Server.MapPath(“Imagem.jpg”))
Dim gr As Graphics = Graphics.FromImage(imagem)

Dim texto As String = “PontoNetPT“
Dim fonte As New Font(“Arial”, 16)
Dim cor As New SolidBrush(Color.White)
Dim posicao As New PointF(2.0F, 2.0F)

gr.DrawString(texto, fonte, cor, posicao)
Response.ContentType = “image/jpeg”

imagem.Save(Response.OutputStream, Imaging.ImageFormat.Jpeg)
gr.Dispose()
imagem.Dispose()

Deve-se importar o Namespace System.Drawing.Image para podermos fazer tais manipulações. Feito isso, a imagem será exibida no controle Image do WebForm1.aspx.