Desenvolvimento - ASP. NET
Performance em aplicações ASP.NET
Este artigo abordará boas práticas de escrita de código, tratamento de Exceptions, validações e principalmente performance em aplicações ASP.NET.
por Israel AéceEste artigo abordará boas práticas de escrita de código, tratamento de Exceptions, validações e principalmente performance em aplicações ASP.NET. Entre estes, vamos ver: validação de dados que o usuário insere no sistema, Data Binding, Cache, ViewState, Session, Server Controls, acesso à dados entre outras características do ASP.NET, que se, mal usadas podem causar um impacto negativo na aplicação desenvolvida.
Sempre quando pensamos em aplicações ASP.NET, nos vem a cabeça uma aplicação rápida e robusta, pois como sabemos a partir do momento que a aplicação estiver disponível para acesso, não conseguimos mais ter controle de quantos ou quais usuários estão acessando a aplicação. Vale lembrar que entre estes usuários sempre existem aqueles que querem de alguma forma destruir e/ou danificar o sistema. Além de termos que garantir uma boa performance, temos que sempre tomarmos cuidado com a segurança, pois é inadimissível aumentar o desempenho com baixa segurança.
Características de Linguagens
Option Strict - Se estiver utilizando Visual Basic .NET, uma boa técnica é ativar a opção Option Stricit. Isso fará com que o Visual Basic .NET não permita conversões de objetos implícitamente. Para ativar isso isso você vai até as propriedades do projeto, opção Builder e marque como "On" a opção Option Strict.
With ... End With - Ainda em Visual Basic .NET, voce pode utilizar a estrutura With ... End With, onde com ela você não precisa mais referenciar o objeto até que se passe pelo End With.
Strings - Quando estiver realizando concatenações de strings é sempre bom avaliar a quantidade e tamanho das strings a serem concatenadas. Com essa análise você poderá fazer o uso ou não do objeto StringBuilder que garante uma concatenação extremamente rápida. Um dica é utilizar o objeto StringBuilder quando não se sabe o número de concatenações que serão realizadas.
IDisposable - Implemente esta Interface em seus objetos que utilizam recursos não gerenciados pelo .NET Framework.
Restringindo acesso na aplicação
Apesar de não ter conhecido à fundo o ASP Classic 3.0, via que a forma de restringir uma área do site, onde somente usuário cadastrados em algum lugar tivesse acesso era através de variáveis de sessão, onde quando o usuário fizesse um Login válido, uma váriavel de sessão era criada com algum valor lá dentro.
Com isso, para não ficar fazendo a verificação da variável de sessão em todas as páginas, um arquivo "include" era criado e dentro dele verificado se a variável de sessão é ou não válida e não sendo, redireciona o usuário à página de Login do sistema. Este arquivo era incluído no topo de todas as páginas da aplicação que são restritas.
No ASP.NET isso muda completamente. Continuar fazendo o que era feito no ASP Classic é programar em ASP.NET pensando no passado. Dentro do ASP.NET você tem classes e infra-estrutura para criar uma área restrita facilmente e segura através do FormsAuthentication. Para saber como criar esta área restrita em ASP.NET utilizando Forms Authentication, pode consultar este artigo. Além disso, o ASP.NET fornece também intrinsicamente formas de autenticação via Windows e Passport.
Passagem de Parâmetros
Este é um ponto bastante interessante no ASP.NET, ou melhor, em aplicações Web no geral. A passagem de parâmetros entre as páginas é sempre algo que necessitamos, onde é com ela que mandamos uma referência à algo que precisamos fazer ou consultar em uma outra página. Esse é um dos pontos que a segurança também deve ser bastante reforçada, pois como sabemos, usuários mal intencionados podem causam um estrago considerável em nossa aplicação. Analisaremos abaixo cada um dos métodos que são possíveis utilizá-los:
Variáveis de Sessão - É interessante para guardar valores que serão "carregados" por toda a aplicação. Temos a vantagem do cliente não saber onde esta sendo "guardado" este objeto e assim não consegue alterá-lo. Já utilizá-lo para apenas passar um simples parâmetro entre páginas não é viável pois degrada a performance, visto que a Session depois de criada demora 20 minutos (padrão) para ser eliminada da memória do servidor, caso ela não seja removida. Para casos como este ela não deve ser utilizada.
QueryString - A QueryString é um objeto bastante usado nas aplicações atuais. É um objeto "leve" e de fácil manipulação. Mas há o inconveniente da mesma ficar exposta ao cliente, podendo ele alterar, colocando ali dado ou script malicioso e assim comprometer a aplicação. É sempre uma boa prática verificar antes de utilizá-la se trata-se de um dado seguro onde podemos passá-lo para o procedimento que o utiliza sem problemas.
Aqui um ponto bastante importante para o tratamento "server-side" de QueryStrings. Vemos bastante casos onde os desenvolvedores utilizam o bloco de tratamento de erros estruturado Try...Catch...Finally para este analisá-la. Além de não ser muito interessante, prejudica a peformance levemente, principalmente se o desenvolvedor ainda apanhar a Exception e não faz nada com ela. Um exemplo desse mal uso é mostrado abaixo:
1 2 3 4 5 6 7 8 Dim clienteID As Integer Try clienteID = Convert.ToInt32(Request.QueryString("ClienteID")) Catch ex As Exception clienteID = 0 End Try "Processa o Código
Como podemos ver no código acima, tentamos converter o valor que é passado através da QueryString "ClienteID" para um inteiro. Se o método falhar uma Expcetion é atirada no Cliente e o pior é que não fazemos nada com ela. Apenas no bloco Catch definimos novamente para 0 o valor da variável e competirá ao programador testá-la se é ou não zero.
Este processo funciona sem problemas, porém não faz se necessário utilizar um tratamento de erros estruturado para isso. Existe uma forma mais elegante de tratar esses valores, que é utilizando Regular Expressions. Através dele não há a necessidade de gerenciarmos, pois através de um método que nos retornará True ou False saberemos se é ou não um número valido. O exemplo abaixo ilustra como fazer isso:
1 2 3 4 5 6 Imports System.Text.RegularExpressions "... Dim clienteID As String = Request.QueryString("ClienteID") If Regex.IsMatch(clienteID, "^[0-9]+$") Then "Processa o Código End If
Nota: Não utilize a propriedade Request.Params quando você sabe de onde o parâmetro está vindo. Esta propriedade percorre as coleções de QueryStrings, Forms, ServerVariables e Cookies, gerando assim um overhead desnecessário.
HttpContext.Items - Considero a melhor forma de passar parâmetros entre páginas. É um objeto "leve" e o cliente não sabe por onde os dados estão sendo enviados. Ele une a segurança das variáveis de sessão e a performance da QueryString. Mas há também um pequeno problema, ou seja, ele existe apenas durante a requisição de HTTP e depois disso ele já desaparece.
Há uma técnica bastante interessante para utilizá-lo. O segredo está na criação de um método compartilhado (Shared em VB.NET e static em C#) na página que receberá o parâmetro. Este método compartilhado deverá ter os parâmetros que você precisará na página de destino. O código abaixo mostra como criá-lo e chamá-lo, onde o cenário é passar parâmetros da página WebForm1 para a página WebForm2:
1 2 3 4 5 6 7 8 9 10 11 12 Public Class WebForm2 Inherits System.Web.UI.Page "... Public Sub Page_Load(...) Handles MyBase.Load Response.Write(HttpContext.Current.Items.Item("NomeCliente")) End Sub "... Public Shared Sub PassaParametros(ByVal nome As String) HttpContext.Current.Items.Add("NomeCliente", nome) HttpContext.Current..Server.Transfer("WebForm2.aspx") End Sub End Class
1 2 3 4 5 6 7 8 Public Class WebForm1 Inherits System.Web.UI.Page "... Public Sub Page_Load(...) Handles MyBase.Load WebForm2.PassaParametros("Microsoft Corporation") End Sub "... End Class
Analisando o código acima, vemos que dentro do método PassaParametros adicionamos o valor que lhe é passado na coleção de Items do HttpContext. Notem que esta é uma coleção do tipo "chave-valor". Além disso fazemos o redirecionamento "server-side" do usuário através do método Server.Transfer. No evento Load do WebForm2, exibimos o valor passado para o usuário.
No WebForm1 ou em qualquer outra parte da aplicação, temos acesso a este método justamente porque ele é compartilhado e não necessita da instância da classe para isso.
Pages - Páginas
Temos várias considereções a serem feitas a nível de página, mas também devem ser estendidas a nível de Web User Controls (arquivos ASCX). Veremos abaixos algumas técnicas para garantir um melhor desempenho da aplicação:
Page.IsPostBack - Utilize sempre a propriedade Page.IsPostBack da classe Page para evitar o processamento redundante. Como o ViewState mantém o estado dos objetos durante o PostBack não é necessário recarregar e/ou fazer as manipulações a nível de página sempre que ela é requisitada.
Page.EnableSessionState - Defina para False quando você não precisa criar/ler variáveis de sessão dentro de uma página específica. Caso precise apenas ler estas variáveis, defina esta propriedade como Readonly.
Response.BufferOutput - Como ele é habilitado por padrão no ASP.NET, convém você analisar o cenário para desabilitá-lo ou não. A sua função é se estiver definido como True, o ASP.NET processará toda a página e depois de concluído, mandará o output para o cliente. Caso contrário, ou seja, se estiver definido como False, o ASP.NET já irá enviando o output para o cliente na medida em que a página vai sendo processada. Um cenário em que isso é usado é quando temos uma página com um processamento custoso e temos clientes que não possuem banda-larga. Você pode utilizar o método Response.Flush para enviar o conteúdo corrente armazenado em buffer até o momento para o cliente, lembrando que pode ser invocado múltiplas vezes durante a requisição. Existem três formas de habilitarmos o Buffer, as quais veremos abaixo:
|
|
Diminuir o tamanho da página - É algo que muitos desenvolvedores não levam em conta. Em design-time, por questões até de pressa ou mesmo de querermos fazer algo mais rápido, acabamos deixando nossa página maior do que ela poderia ser com espaços em brancos e "TABs" desnecessários que aumentarão consideravelmente o tamanho da página a ser enviada para o cliente. O que se tem a fazer neste caso é remover todos os caracteres que não são necessários para apresentação da página. Outro cuidado que devemos ter, para diminuirmos o tamanho, é separar o código Javascript e estilos em outros arquivos, *.js e *.css respectivamente, e depois anexados a página, como é mostrado abaixo:
|
|
Redirecionamento de usuários - Pelo fato de que o método Response.Redirect força o cliente à emitir um novo pedido, torna o método menos eficiente que o Server.Transfer, que evita esta ação. Quando o Server.Tranfer é executado, percebe-se que a URL no browser não é alterada. Ambos os métodos invocam o método Response.End, que dispara uma Exception do tipo ThreadAbortException, tem a finalidade de abortar a requisição corrente.
Um erro bastante comum (Thread was being aborted.), é quando chamamos o método Response.Redirect dentro de um bloco Try. O problema é que ele invoca o método Response.End, que este por sua vez atira uma Exception, provavelmente não executará o código de acordo como queremos, pois o Catch irá ser executado.
Para contornar este problema, devemos utilizar a sobrecarga do método Redirect, onde passamos um valor booleano para indicar se o método Reponse.End deve ou não ser invocado. Definindo este parâmetro como False, a execução do código continua sem problemas, não interferindo assim na lógica do código. Abaixo o código que exemplifica a chamada deste método:
1 2 3 4 5 6 Try "Código... Response.Redirect("Pagina.aspx", False) Catch ex As Exception "Tratamento de erro... End Try
Validação Client-Side - utilize os controles Validators que o ASP.NET fornece para o tratamento de dados que os usuários inserem do lado do cliente. Com eles você também pode customizar a validação através do JavaScript. É importante deixar claro que isso não dispensa a validação do lado do servidor.
ViewState é o mecanismo qual o ASP.NET utiliza para manter o estado de controles e objetos quando um Postback ocorre na página. As informações são armazenadas em um controle do tipo hidden chamado _VIEWSTATE. Apesar de ser um recurso interessante, compromete a performance quando é mal utilizado, pois há ocasiões onde ele não é necessário, e vale lembrar que por padrão ele é habilitado.
Leve em consideração os cenários abaixo para a não utilização do ViewState:
Quando a página não dispara Postback.
Quando os eventos dos server-controls não são disparados.
Quando você popula os controle a cada Refresh da página.
|
|
Exceptions
É bastante complicado, mas temos que escrever códigos para evitar Exceptions. Uma boa dica para gerenciar as Exceptions que são geradas pela aplicação é através do evento Application_Error do arquivo Global.asax, que é sempre disparado quando uma Expcetion na aplicação ocorre. Um exemplo simples seria algo como o código mostrado abaixo:
|
|
Através do método Server.GetLastError() você recupera a última Exception que ocorreu na aplicação e faz o log da mesma no local que você desejar.
O que também devemos nos atentar é com relação a quando disparar (Throw) Exceptions entre camadas ou mesmo quando chamamos uma determinada função. Isso gera uma perda de performance e obriga também o cliente a tratá-la. Uma técnica comumente utilizada é a criação de um Enumerador e as funções, quando possível, retorna o status da execução do código, baseado neste enumerador. Exemplo:
|
|
Depois do Enumerador DBStatus criado, um método, como por exemplo que adicione dados dentro de uma Base de Dados que passará a retornar o status da execução e assim você analisa no cliente e baseado nisso, você informa sucesso ou falha.
|
|
Acesso à Dados
Sempre que possível, é interessante utilizar o objeto DataReader para a leitura de dados de uma base de dados qualquer. Isso porque é o objeto mais rápido para se resgatar os dados e exibí-los ao cliente. Mas vale levar em consideração que ele é somente-leitura (readonly) e somente avança (foward-only) e com isso necessita de uma conexão ativa até que se percorra todos os dados retornados pela query, requerendo uma atenção especial do desenvolvedor para atentar-se ao fechamento do mesmo.
Uma boa prática é paginarmos os dados retornados pela query, retornando da base de dados somente os registros correspondentes a página qual o usuário requerer. Além de diminuir o tráfego de dados na rede, isso nos proporcionará um grande ganho de performance. O que também deve ser considerado é a possibilidade de armazenar os dados que são retornados em cache de aplicação, onde assim, o custo de resgatar os dados é apenas uma única vez, já que o cache é compartilhado entre todos os usuários da aplicação. Para um exemplo completo de como proceder neste tipo de paginação, consulte este artigo.
Data Binding
Algo que perdemos performance é quando utilizamos o método DataBinder.Eval em DataBindings, geralmente para resgatarmos os dados das colunas da nossa fonte de dados em controles como Repeater e DataList. Isso porque este método utiliza Reflection para avaliar os argumentos e retornar o resultado. O exemplo abaixo mostra o exemplo do uso deste método:
|
|
Uma alternativa a este método é utilizar a conversão explícita do DataItem para o objeto, que varia de acordo com a sua fonte de dados. Se tua fonte de dados for do tipo DataReader, então o teu DataItem será do tipo DbDataRecord (que encontra-se dentro de System.Data.Common). O código acima, com a conversão explícita, fica da seguinte forma:
|
|
Nota: Caso a sua fonte de dados seja do tipo Dataset/DataTable, o seu DataItem deve ser convertido para DataRowView. Se quiser saber um pouco mais sobre isto, pode consultar este post.
Caso você tenha vários campos que são exibidos ao usuário, o que você deverá fazer para ganhar ainda mais performance, é utilizar o evento ItemDataBound do seu controle, pois assim, fará a conversão apenas uma única vez. O código abaixo ilustra este processo:
|
|
Caching
Como Cache do ASP.NET é algo bastante extenso e muito útil, e também não quero prolongar o artigo para não desgastar o leitor, opto por apenas indicar um ótimo artigo de Miguel Ferreira que encontra-se publicado no site do MSDN Brasil neste endereço.
CONCLUSÃO: Como vimos neste artigo, há uma série de recursos que o ASP.NET nos fornece, porém devemos saber como utilizá-los, caso contrário ao invés do recurso nos ajudar, pode prejudicar. Temos que nos atentarmos aos detalhes, que como vimos, por serem simples muitas vezes passam despercebidos e compromete a qualidade de nossa aplicação.
|