Convertendo HTML em PDF

Nesse artigo veremos como converter um HTML em PDF. O HTML pode vir de uma página externa quanto ser criado em tempo de execução. Não haverá muito o que explicar pois o código em si, você encontra a rodo pela net. Usaremos o componente iTextSharp.

Baixe o binário e adicione em seu aplicativo (pasta Bin). Utilize o código abaixo para fazer o processo de conversão e exibição do PDF na tela:


using System;
using System.Web;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.text.html.simpleparser;


            // Captura ou atribui o HTML a ser convertido - no caso estou baixando de uma URL
            string html = new System.Net.WebClient().DownloadString("http://thiagomarcal.blogspot.com/");
            //string html = "Convertendo o HTML em PDF - Thiago Marçal";
            // Cria o documento aplicando o tamanho e margens
            Document documento = new Document(PageSize.A4, 80, 50, 30, 65);
            // Memory Stream para ser usado na conversão e emissão
            MemoryStream ms = new MemoryStream();
            // Inicializa o gravador
            PdfWriter writer = PdfWriter.GetInstance(documento, ms);
            // Lê o HTML e atribui
            StringReader conteudo = new StringReader(html);
            // Objeto de conversão do HTML
            HTMLWorker objeto = new HTMLWorker(documento);
            // Abre o documento
            documento.Open();
            // Aplica o parser para análise de conversão
            // Geralmente aqui ocasiona muitos erros - irei explicar no post
            objeto.Parse(conteudo);
            // Fecha o documento
            documento.Close();
            // Força o download do PDF gerado - se desejável você pode salvar em disco também
            Response.Clear();
            Response.AddHeader("content-disposition", "attachment; filename=Documento_HTML.pdf");
            Response.ContentType = "application/pdf";
            Response.Buffer = true;
            Response.OutputStream.Write(ms.GetBuffer(), 0, ms.GetBuffer().Length);
            Response.OutputStream.Flush();
            Response.End();


Veja que a variável html eu atribuo o HTML que desejo converter. O seu conteúdo pode vir externamente ou posso atribuir um valor desejado.

Vamos aos problemas! Muitos vão encontrar erro quando o objeto estiver fazendo o parser do HTML e muitos irão falhar. Acontece que, devido à complexidade do HTML que estiver trabalhando, o componente não consegue converter para o formato adequado ao que ele usa internamente. Por exemplo, o TABLE do HTML que usamos é "meio" diferente do TABLE que o componente usa. Esse é um dos exemplos... Então prepara-se para fazer um tratamento adequado antes de fazer o parser. Eu sugiro o seguinte:
  1. Use expressão regular para validar seu HTML;
  2. Use expressão regular e/ou o HtmlAgilityPack para remover os JavaScripts;
  3. O conteúdo a ser convertido deve ser apenas o que está dentro do body;
  4. Imagens, CSS e links devem conter o caminho completo (exemplo: http://thiagomarcal.blogspot.com/imgs/logo.png ao invés de ../imgs/logo.png), etc.
Ou seja, quanto mais simplificado for seu HTML mais rápido e fácil será convertido em PDF. Uma observação bem clara é: nem sempre ficará 100% que o esperado.

Uma dica que pode ser usada (e um armengue, claro!) é fazer o seguinte:
  1. Gere um thumb (imagem) do HTML que deseja converter (saiba como gerar um thumb de um HTML nesse post) e salve em disco;
  2. Atribua a variável html com o conteúdo "<img src="http://thiagomarcal.blogspot.com/imgs/thumb.png" />";
  3. Coloque o width e height na tag IMG o tamanho desejado (pode ser até o tamanho do papel).
Pronto! O parser irá identificar apenas uma imagem no HTML e gerará o PDF com ela. Mole-mole...

Acompanhar rastreamento de pedido dos Correios com C#

Mais um artigo para quem trabalha com loja virtual... Essa funcionalidade é bem importante para visualizar o rastreamento de um determinado pedido. Ao invés de mandar apenas o link com o código para seu cliente, porque não enviar logo a tabela com os dados? Pois bem, é isso que iremos fazer! O código é bem simples, rápido e pode-se alterar para uma "infinidades" de formas afim de o colocar do jeito que deseja.

Abaixo temos um resumo do processo:

  1. Enviamos uma requisição GET com o número do rastreamento;
  2. Obtemos os dados de retorno (página completa);
  3. Capturamos apenas a tabela com os dados de rastreamento;
  4. Exibimos na tela.
Para capturar a apenas a tabela com os dados, pode-se fazer de várias formas (Expressão Regular, etc), mas preferi usar um componente que captura nós de um documento HTML, o HtmlAgilityPack. Baixe ele e o coloque na referência de seu projeto (Bin). Agora é fácil - utilize o código abaixo:

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using HtmlAgilityPack;

protected void Page_Load(object sender, EventArgs e)
    {

        // Aplica campos de parâmetros
        string codigo = "ES659170465BR";
        string parametros = "?P_COD_UNI=" + codigo + "&P_LINGUA=001&P_TIPO=001";
        // Cria o objeto de requisição
        WebRequest requisicao = WebRequest.Create("http://websro.correios.com.br/sro_bin/txect01$.QueryList" + parametros);
        // Realiza a requisição
        HttpWebResponse retorno = (HttpWebResponse)requisicao.GetResponse();
        // Lê o objeto e faz a atribuição à variável
        StreamReader stream = new StreamReader(retorno.GetResponseStream(), Encoding.GetEncoding("ISO-8859-1"));
        string dados = stream.ReadToEnd();
        // Transforma em um documento HTML - HtmlAgilityPack
        HtmlDocument html = new HtmlDocument();
        html.LoadHtml(dados);
        // Captura apenas a tabela contendo os dados do envio
        HtmlNode tabela = html.DocumentNode.SelectSingleNode("//table");
        if (tabela != null)
        {
            // Nesse caso você pode capturar as colunas e linhas e trabalhar conforme desejar
            // Apenas fiz o loop para remover a formatação inicial da tabela
            string htmlTable = "<table>";
            // Extrai as linhas da tabela
            foreach (HtmlNode linha in tabela.SelectNodes("//tr"))
                htmlTable += "<tr>" + linha.InnerHtml + "</tr>";
            htmlTable += "</table>";
            // Exibe a tabela de rastreamento
            Response.Write("O horário não indica quando a situação ocorreu, mas sim quando os dados foram recebidos pelo sistema, exceto no caso do <b>SEDEX 10 e do SEDEX Hoje, </b>em que ele  representa o horário real da entrega.<br><br>" + htmlTable);
        }
        else
            Response.Write("O nosso sistema não possui dados sobre o objeto informado. Se o objeto foi postado recentemente, é natural que seus rastros não tenham ingressado no sistema, nesse caso, por favor, tente novamente mais tarde. Adicionalmente, verifique se o código digitado está correto: " + codigo);
        // Finaliza objetos
        stream.Close();
        retorno.Close();

    }

No caso peguei o HTML da tabela contendo os dados e exibi de vez na tela. Poderia tratá-la de várias formas e uma delas poderia ser transformando-a em um DataTable ou DataSet para alguma manipulação posterior. Veja nesse link aqui como fazer isso.

Os Correios não dispõe de Web-Service para esse tipo de consulta. Olhando na net, encontrei dois artigos: um de Manoel Campos e outro de Carlos Ferrari onde ambos dispõe de Web-Services (em PHP - mas pode ser consumido pelo ASP.NET) para esse tipo de consulta. Quem não quiser implementar e apenas consumir, podem usá-los que chega o mesmo objetivo.

Artigo bem fácil e rápido! Espero ter ajudado...

Redimensionar imagens com alta qualidade em ASP.NET

O código que irei mostrar nem precisarei de muita explicação. Você encontra a rodo aí pela internet... Contudo, esse tem um pequeno diferencial: misturei o código "pescado" da net com o incremento de Glenn Jones. O redimensionamento consiste em colocar no SRC da imagem o Generic Handler que faz todo o processo e gera a imagem. Ou seja, crie um arquivo ImageResize.ashx e coloque o seguinte código abaixo:


using System;
using System.Drawing;
using System.IO;
using System.Web;


public class ImagemHandler : IHttpHandler {
    
    public void ProcessRequest (HttpContext context) {
        
        // Recupera os parâmetros passados pela página
        string strSrcImagemOriginal = "../upload/" + context.Request["img"].ToString();
        string strAlturaImagemRedimensionar = "";
        string strLarguraImagemRedimensionar = context.Request["w"].ToString();        
        
        // Cria temporariamnte a imagem
        System.Drawing.Image imagemTemp = System.Drawing.Image.FromFile(context.Server.MapPath(strSrcImagemOriginal));
        
        // Variáveis contendo o tamanho
        int srcWidth = imagemTemp.Width;
        int srcHeight = imagemTemp.Height;
        int thumbHeight;
        int thumbWidth;
        
        // Redimensiona a largura de forma proporcional
        if (context.Request["w"] != null)
        {
            thumbWidth = int.Parse(strLarguraImagemRedimensionar);
            thumbHeight = (int)(thumbWidth * imagemTemp.Height) / imagemTemp.Width;
        }
        else
        {
            thumbWidth = imagemTemp.Width;
            thumbHeight = imagemTemp.Height;
        }
        
        // Redimensiona a altura
        if (context.Request["h"] != null)
        {
            strAlturaImagemRedimensionar = context.Request["h"].ToString();
            thumbHeight = int.Parse(strAlturaImagemRedimensionar);
            // Faz o rateio para o redimensionamento proporcional
            // Assim a altura e a largura nunca irão ultrapassar o tamanho limite
            double widthRatio = (double)imagemTemp.Width / (double)thumbWidth;
            double heightRatio = (double)imagemTemp.Height / (double)thumbHeight;
            double ratio = Math.Max(widthRatio, heightRatio);
            thumbWidth = (int)(imagemTemp.Width / ratio);
            thumbHeight = (int)(imagemTemp.Height / ratio);
        }
        imagemTemp.Dispose();
        
        // Envia para a memória o objeto a ser trabalhado bem como o novo objeto
        Stream objStream = new StreamReader(context.Server.MapPath(strSrcImagemOriginal)).BaseStream;
        BinaryReader objBinaryReader = new BinaryReader(objStream);
        int i = (int)objStream.Length;
        byte[] arrBytes = objBinaryReader.ReadBytes(i);
        System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(arrBytes);
        System.Drawing.Image image = System.Drawing.Image.FromStream(memoryStream);
        System.Drawing.Image thumbnail = new Bitmap(thumbWidth, thumbHeight);
        System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage(thumbnail);
        
        // Melhoria da nova imagem
        graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
        
        // Desenha a nova imagem
        graphic.DrawImage(image, 0, 0, thumbWidth, thumbHeight);


        // Aplica a codificação necessária
        System.Drawing.Imaging.ImageCodecInfo[] info = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
        System.Drawing.Imaging.EncoderParameters encoderParameters;
        encoderParameters = new System.Drawing.Imaging.EncoderParameters(1);
        encoderParameters.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
        
        // Exibe a imagem em forma de JPG
        context.Response.ContentType = "image/jpeg";
        thumbnail.Save(context.Response.OutputStream, info[1], encoderParameters);


    }


}

Mais dado do que isso é só de mão beijada!

Verificando Vulnerabilidades em Aplicações Web

Dica rápida para analisar a segurança de aplicações web... A Locaweb disponibilizou um artigo acerca do uso do WEBSECURIFY (da Google) na qual realiza uma bateria de testes sob uma aplicação web e exibe as vulnerabilidades que possui. Mais informações leiam aqui. Bem interessante...

Melhorando o desempenho de aplicações ASP.NET que usam AJAX

Alguns dias atrás estava lendo alguns artigos sobre desempenho e achei um artigo interessante de LanceZhang na qual ele fez uma bateria de testes em um website que possuia controles ASP.NET AJAX. O artigo você lê na íntegra aqui. Mas, como se diz na web, "é old mas é gold!", aproveitei o artigo dele para resumir (tirar o quente) das configurações que ele aplicou e os colocarei aqui. Para quem sabe inglês o artigo é indispensável a leitura, pois lá ele mostra com detalhes os testes realizados bem como os gráficos de desempenho.

Bem, o que ele fez? Encheu uma página de controles AJAX e primeiramente mediu o tráfego na rede averiguando a quantidade de bytes que são carregados quando feito uma requisição, sendo ela quando dado um PostBack ou apenas no Load da página. Quem tem o Firefox, com certeza deve ter o plugin Firebug instalado. No Firebug tem uma sessão de monitoramento de Rede que analisa as chamadas realizadas.


A primeira coisa notada é o tamanho da página que estava muito grande. O uso do cache reduzia bruscamente o tamanho da página sem fazer novos carregamentos desnecessários. Juntamente com a compressão do ScriptResource que reduz ainda mais o tamanho dos scripts gerados.  Então, no web.config, devemos aplicar a seguinte configuração:

<system.web.extensions>
<scripting>
<scriptResourceHandler enableCompression="true" enableCaching="true" />
</scripting>
</system.web.extensions>

Faça um novo teste e notará a diferença! Outro aplicação de desempenho é a forma como o ScriptManager do AJAX é trabalhado. Então é sugerido usá-lo com a seguinte configuração:

<asp:ScriptManager ID="ScriptManagerAjax" runat="server" EnablePartialRendering="false" ScriptMode="Release" LoadScriptsBeforeUI="false">
</asp:ScriptManager>

Cuidado com o EnablePartialRendering! Faça o teste em sua aplicação com os valores false ou true porque a depender do que você usa em seu sistema isso muda muito no comportamento dos scripts. Por último, você pode usar o CompositeScript dentro do ScriptManager para agregar várias chamadas de scripts em uma só. Veja lá no site de LanceZhang como fazer, caso tenha interesse nessa parte e se isso ainda não foi o suficiente.

Agregado a isso, e fora do escopo, você pode usar a compressão/compactação do ViewState para minimizar o tamanho da página. Você pode encontrar artigos relacionados por aí na net, mas aconselho dar uma lida nesse aqui ou esse a depender de como queira utilizar. Muitas vezes eu prefiro desabilitar o ViewState... Mais rápido, só que com cautela!

Calculando frete com Web-Service dos Correios

Muito utilizado, principalmente, em lojas virtuais, o cálculo de frete é uma funcionalidade bastante importante para todo e qualquer sistema. Nesse artigo veremos como usufruir do Web-Service dos Correios e fazer o cálculo de frete para um determinado peso, local e tipo de frete. Pelo Web-Service é possível consultar SEDEX, e-SEDEX e PAC. Lembrando que, para o tipo e-SEDEX e outros, a empresa deve ter um contrato com os Correios para utilizar essa modalidade. Isso será mostrado como informar o login dos Correios antes de fazer a consulta.


Antes de começar, veja abaixo as formas de consulta que os Correios dispõe (retirado do site):

Forma de acesso e respostaDefiniçãoExemplos de usoProtocolos de Acesso e de Resposta
WebserviceA consulta utiliza a tecnologia webservices – coleção de protocolos e padrões usados para troca de informações entre aplicações Internet.Lojas virtuais 
Sistemas corporativos
Acesso e resposta via XML, SOAP e WSDL - protocolos dewebservices
Resultado XMLA consulta retorna os dados em formato XML, para livre tratamento das informações pelo cliente.Lojas virtuais 
Sites de empresas
Acesso via post
Resposta padrão XML
Resultado página do clienteA consulta transfere as informações de resposta para uma página do cliente, por ele especificada.Sites de empresasAcesso via post 
Resposta via get
Resultado página dos CorreiosA resposta vem numa página dos Correios, em formato de página pop-up.Call-centersAcesso via post 
Resposta em página dos Correios
Conforme explicado, vamos utilizar o Web-Service para consumir e obter a resposta desejada. Nesse link tem o manual com maiores detalhes acerca do uso. Vamos a implementação!

Crie/Abra um Web Site e clique com o direito sobre o projeto para abrir o menu. Escolha a opção Add Web Reference... .


Informe a URL http://ws.correios.com.br/calculador/CalcPrecoPrazo.asmx e clique em Go. Aguarde a realização da busca pelo Web-Service, renomeie o serviço (caso deseje) e escolha Add Reference.


Veja que em seu projeto irá surgir uma pasta nova denominada App_WebReferences e seu web.config terá uma nova entrada. Agora, preparemos o seguinte método (são apenas sugestões, pode melhorá-la ou adequar a sua necessidade):


private string ConsultaWebService()
    {
        // Dados da empresa, se tiver contrato com os Correios
        string nCdEmpresa = string.Empty;
        string sDsSenha = string.Empty;
        // Código do tipo de frete - por padrão deixei o SEDEX
        string nCdServico = "40010";
        // Cep de origem e destino - apenas números
        string sCepOrigem = "40280000";
        string sCepDestino = "40280000";
        // Peso total da encomenda - por padrão deixei 1kg
        string nVlPeso = "1";
        // Formato da encomenda - por padrão deixei caixa
        int nCdFormato = 1;
        // Para encomenda do tipo PAC, deve-se preencher a dimensão da embalagem
        decimal nVlComprimento = 0;
        decimal nVlAltura = 0;
        decimal nVlLargura = 0;
        decimal nVlDiametro = 0;
        // Informa se é por mão própria - por padrão deixei Não
        string sCdMaoPropria = "N";
        // Valor declarado - por padrão não informo
        decimal nVlValorDeclarado = 0;
        // Se desejo recebr aviso de recebimento - por padrão não quero
        string sCdAvisoRecebimento = "N";


        // Instancio o web-service
        Correios.CalcPrecoPrazoWS webServiceCorreios = new Correios.CalcPrecoPrazoWS();


        // Efetuo a requisição
        Correios.cResultado retornoCorreios = webServiceCorreios.CalcPrecoPrazo(nCdEmpresa, sDsSenha, nCdServico, sCepOrigem, sCepDestino, nVlPeso, nCdFormato, nVlComprimento, nVlAltura, nVlLargura, nVlDiametro, sCdMaoPropria, nVlValorDeclarado, sCdAvisoRecebimento);


        // Verifico se há retorno
        if (retornoCorreios.Servicos.Length > 0)
        {
            // Se deu tudo certo, então retorna o valor
            if (retornoCorreios.Servicos[0].Erro == "0")
                return "R$ " + retornoCorreios.Servicos[0].Valor;
            else
                return retornoCorreios.Servicos[0].MsgErro;
        }
        else
            return "NÃO FOI POSSÍVEL CONSULTAR O SERVIÇO DESEJADO!";


    }


No caso, o array de serviços veio apenas um pois informando na variável nCdServico que era para consultar apenas SEDEX. Pode ser mais de um numa consulta separados por vírgula, exemplo "40010,81019".

A tabela de serviços disponíveis são:

Código  Serviço
41106  PAC sem contrato
40010  SEDEX sem contrato
40045  SEDEX a Cobrar, sem contrato
40215  SEDEX 10, sem contrato
40290  SEDEX Hoje, sem contrato
40096  SEDEX com contrato
40436  SEDEX com contrato
40444  SEDEX com contrato
81019  e-SEDEX, com contrato
41068  PAC com contrato


Essas e outras informações você encontra no manual dos Correios. Isso possibilita e facilita bastante ao programador na consulta e obtenção dessa informação. Nunca escrevi tanto a palavra Correios em minha vida... Té!