Clique aqui para ler este artigo em pdf imagem_pdf.jpg

msdn03_capa.jpg

Clique aqui para ler todos os artigos desta edição

 

Crie seu Weblog com ASP.NET, JavaScript e OleDB (Parte I)

por Marco Bellinaso

 

Nos dias atuais, parece que todo mundo quer um blog. No entanto, você não encontrará nenhum código para blog predefinido no ASP.NET que contenha os recursos mostrados neste artigo. A grande vantagem de construir seu próprio aplicativo de blog é que você adquire muita prática com controles de servidor (Server Controls) ASP.NET, como Repeater, DataList e Calendar. A primeira vista, um aplicativo de blog pode parecer um exercício simples, mas na verdade ele requer que você implemente muitos recursos necessários em um típico aplicativo de geração de relatórios, tais como criar e implantar relacionamentos Master-Detail, editar e excluir registros, ocultar ou exibir conteúdos e controles de acordo com o usuário e gerenciar validação de dados para múltiplos formulários na mesma página.

Este artigo apresenta os detalhes de design e de implementação de um projeto Web e explica as técnicas que podem ser facilmente aplicadas a uma variedade de projetos em ASP.NET, independentemente de terem sido criados para fins de negócios ou divertimento.

Antes de começar a criar o código, você deve decidir que tipo de blog deseja criar, quais recursos ele conterá e como será projetado o armazenamento de dados. Um blog eficaz deverá conter vários recursos diferentes. As mensagens do blog devem ser mostradas na ordem da mais nova para a mais antiga. É possível postar várias mensagens no mesmo dia e agrupá-las visualmente em uma tabela, distinguindo-as como mensagens separadas, ordenadas cronologicamente. Além disso, o usuário deve ser capaz de selecionar um intervalo de tempo para as entradas que deseja ler. Isso é importante porque você não quer recuperar um conteúdo antigo, que já tenha sido visto pelo usuário.

O usuário pode fazer comentários sobre qualquer mensagem, e os comentários postados devem ser mostrados diretamente abaixo da mensagem a que se referem, facilitando o acompanhamento da thread. Além disso, o proprietário do blog deve ser capaz de postar, editar e excluir mensagens e comentários, enquanto o usuário só poderá ler mensagens e postar comentários. Para permitir ou negar que um certo usuário poste e edite dados, é necessário que alguns controles sejam exibidos ou ocultos e requeiram alguma forma de autenticação.

 

O Design do Banco de Dados

Em seguida, você precisa decidir como deseja armazenar as mensagens e comentários. Neste projeto, foi utilizado um banco de dados SQL Server™, mas o código de exemplo inclui também um banco de dados do Microsoft® Access, caso você prefira usa-lo. O banco de dados contém apenas duas tabelas: uma para as mensagens e outra para os comentários. A tabela de mensagens armazena IDs exclusivas, um título ou cabeçalho opcional, o texto da mensagem e a data e hora em que a mensagem foi postada. A tabela de comentários armazena IDs exclusivas, a ID da mensagem à qual o comentário se refere, o nome do autor, o endereço de e-mail do autor, o texto do comentário e a data e hora em que a mensagem foi postada. A Figura 1 mostra o projeto das tabelas.

 

image001.gif

Figura 1 Tabelas Pai/filho

 

Como você vê, existe uma relação “um para muitos” entre as duas tabelas, vinculadas ao campo MessageID, que também reforça a integridade referencial com exclusões e atualizações em cascata. Observe que os nomes da tabela começam com o prefixo "Blog_". Isso facilita a localização no Enterprise Manager do SQL Server quando forem listadas alfabeticamente. Além disso, os planos de hospedagem para Web geralmente oferecem apenas um banco de dados SQL Server, que você precisa dividir entre todos os módulos de Web que utiliza. Se você não usou um prefixo de tabela, poderá se deparar com um módulo que já use uma tabela denominada Messages, e solucionar esse tipo de problema de conflito de nomes não será tão simples e rápido durante a implantação.

Outro detalhe importante a ser lembrado é que nenhum campo deve ser nulo (null), mesmo que o usuário o deixe em branco. Em vez de permitir valores nulos, você deve definir os campos vazios como uma seqüência de caracteres vazia, a fim de impedir Exceptions não-tratadas. Se você usar Web Forms para entrada de dados, não haverá problema, porque os controles retornarão uma seqüência de caracteres vazia se não houver conteúdo.

 

A Camada de Negócio

Em um aplicativo de banco de dados típico, você tem as camadas de dados, negócio e apresentação. A camada de dados pode consistir em um conjunto de stored procedures, enquanto a camada de negócio pode ser um conjunto de classes (opcionalmente compiladas em sua própria assembly separada) que empacotam as stored procedures para garantir a integridade dos dados, validações e reforçar outras regras de negócio. No entanto, este projeto não conterá stored procedures; os comandos (commands) e consultas SQL foram codificados diretamente na assembly principal para facilitar o uso do blog com o MS-Access. Também não foram separadas as classes para as camadas de dados e negócios, mas como há apenas duas tabelas e somente regras de negócio simples, foi criada uma única classe, que se encarregará de validar a entrada e executar as instruções SQL apropriadas. Todo o código estará na mesma assembly, junto com a camada de apresentação, porque a classe de negócio não será atualizada separada do programa que a utiliza e tampouco será distribuida a classe de negócio com outros tipos de aplicativos ou nos clientes. Desse modo, não há problema em compilá-la com o aplicativo ASP.NET principal (denominado WebLogger). A classe de negócio é chamada Blog e está localizada em um namespace denominado "Business", a fim de que não entrar em conflito com as classes codebehind. Veja como implementar o método que adiciona uma nova mensagem:

 

Public Sub InsertMessage(ByVal title As String, ByVal message As String)

    Dim cmd As New OleDbCommand("INSERT INTO Blog_Messages (Title, _

        Message) VALUES (?, ?)")

    cmd.Parameters.Add("Title", OleDbType.VarChar).Value = title

    cmd.Parameters.Add("Message", OleDbType.LongVarChar).Value = _

        message.Replace(m_brChar, "<br>")

    ExecuteCommand(cmd)

End Sub

 

O método obtém todos os valores de que necessita para adicionar um novo registro. Não é preciso passar o ID da mensagem, já que ela é incrementada automaticamente na tabela do banco de dados e o AddedDate usa a data atual como o valor padrão. O método define um comando SQL INSERT e o executa com o método ExecuteCommand. Observe que o valor do parâmetro Message é o texto da mensagem passado como argumento, ao passo que os caracteres de “nova linha” são substituídos pela tag HTML <br>. O administrador tem permissão para usar a formatação HTML em suas postagens, mas como existem muitas novas linhas, elas são automaticamente convertidas em HTML para poupar o trabalho de digitação. Como você já deve ter adivinhado, o método ExecuteCommand simplesmente executa o objeto de comando que recebe como entrada:

 

Private Sub ExecuteCommand(ByVal cmd As OleDbCommand)

    cmd.Connection = m_Connection

    Try

        m_Connection.Open()

        cmd.ExecuteNonQuery()

    Finally

        m_Connection.Close()

    End Try

End Sub

 

O método ExecuteCommand define a conexão do comando, que é criada no método construtor da classe por meio de uma seqüência de caracteres de conexão (connection string)  lida de uma custom key definida na seção appSettings do arquivo web.config. Em seguida, a conexão é aberta, o comando é executado em um bloco Try, e a conexão é fechada no bloco Finally correspondente. A conexão será fechada mesmo que o método ExecuteNonQuery gere uma Exception. No entanto, não existe nenhum bloco Catch porque não é preciso executar nenhuma operação específica de negócio, como reverter uma transação ou registrar um erro em um arquivo de log. Podemos apenas capturar e tratar a Exception diretamente na página de chamada, por exemplo, para mostrar ao usuário uma mensagem amigável de erro. Os outros métodos Insertxxx, Updatexxx e Deletexxx funcionam de forma similar, por isso não foram abordados aqui. No entanto, recomenda-se examinar o método InsertComment porque, além de adicionar um novo registro à tabela Blog_Comments, ele também envia um e-mail de notificação com o texto de comentário e algumas das mensagens referem-se ao proprietário do blog.

Com esse recurso ativado, o proprietário do blog não precisa carregar contínuas vezes o blog para verificar se há novos e-mails. É claro, se o blog for muito popular e receber bastantes comentários, você terá sempre muitos e-mails. Desse modo, o arquivo web.config do aplicativo deverá ter uma custom key que permita que o administrador decida se quer ser notificado por e-mail a respeito de qualquer novo comentário. Contudo, será necessária outra custom key para armazenar o endereço de e-mail de destino. Essas duas configurações são denominadas, respectivamente, Blog_SendNotifications e Blog_AdminEmail, e são declaradas na seção appSettings do arquivo web.config (juntamente com a custom key da connection string), da seguinte maneira:

 

<add key="Blog_SendNotifications" value="1" />

<add key="Blog_AdminEmail" value="mbellinaso@vb2themax.com" />

 

O valor 1 em Blog_SendNotifications significa que o recurso de notificação está ativo. Qualquer outro valor ou a ausência da chave indica que ele está inativo.

Após inserir o novo comentário e verificar se a notificação de comentário está ativada, você precisará criar o corpo do e-mail. Além do texto do comentário, você precisa incluir também o título, a data e a hora em que a mensagem-pai foi postada, a fim de que haja uma conexão clara entre a mensagem original e os comentários. A recuperação dos dados da mensagem-pai é executada pelo código a seguir com um comando que recupera os campos apropriados a partir do registro da mensagem em questão.

 

Dim sendNotifications As String = ConfigurationSettings.AppSettings( _

    "Blog_SendNotifications")

If Not sendNotifications Is Nothing AndAlso sendNotifications = "1" Then

    Dim msgTitle, msgDate As String

    ' lê os detalhes da mensagem-pai

    cmd.CommandText = "SELECT Title, AddedDate FROM Blog_Messages" & _

        "WHERE MessageID = " & messageID

    m_Connection.Open()

    Dim reader As OleDbDataReader = cmd.ExecuteReader( _

        CommandBehavior.CloseConnection)

    If reader.Read Then

        msgTitle = reader("Title")

        msgDate = CType(reader("AddedDate"), Date).ToString("hh:mm tt" & _

           "on dddd, MMMM dd")

        reader.Close()

    End If

•••

 

Uma vez obtidos todos os dados necessários, você pode criar e enviar a mensagem de e-mail. O texto do e-mail é criado por meio de uma chamada para o método String.Format, que utiliza uma seqüência de caracteres modelo com namespaces para números (no formato {n}) e os valores que substituem os diversos namespaces no modelo:

 

' criar o conteúdo da mensagem

Dim msg As String = String.Format( _

   "{0} (email: {1}) acabou de postar um comentário na mensagem ""{2}"" " & _

   "de seu BLOG que você postou em {3}. Aqui está o comentário:{4}{4}{5}", _

   author, email, msgTitle, msgDate, Environment.NewLine, comment)

' envie a mensagem

System.Web.Mail.SmtpMail.Send("WebLogger", _

    ConfigurationSettings.AppSettings("Blog_AdminEmail"), _

    "Notificação de novo comentário ", msg)

End If

 

O elemento mais importante da classe de negócio é o método GetData, que recupera as mensagens postadas no intervalo especificado, juntamente com seus respectivos comentários. Aqui você precisa escolher se deseja usar um DataReader ou um DataAdapter (com um DataSet) para recuperar e ler os dados. Quando se trata de aplicativos Web, o DataReader é geralmente a melhor opção, especialmente se você não precisa editar nem manter uma cópia dos dados em cache. No entanto, usaremos o DataSet porque ele permite armazenar várias tabelas e criar relacionamentos pai-filho entre elas. Isso facilita recuperar e navegar em todos os registros pai-filhos de uma única vez sem executar consultas separadas, como seria necessário se fosse utilizado o DataReader. Esse método é mais simples para o programador e poupa os recursos do banco de dados e o tráfego na rede, já que são enviadas menos instruções SQL ao banco de dados.

A capacidade de criar relacionamentos entre as tabelas é outro recurso útil porque você pode adicionar colunas calculadas às tabelas, com expressões referentes a outras tabelas no relacionamento. Crie um relacionamento entre as tabelas Blog_Messages e Blog_Comments e vincule-as ao campo MessageID, assim como definido no banco de dados físico na Figura 1. O objetivo era fazer com que o DataTable com os dados do Blog_Messages tivesse uma coluna calculada que retornasse o número de comentários-filho relativos ao registro dessa mensagem. Se fosse utilizado um DataReader, esse método exigiria consultas separadas ou, pelo menos, uma subconsulta (coisa que não é possível com o MS-Access). Com um DataSet, essa ação é realizada com a cópia desconectada dos dados, sem pedir ao banco de dados que conte os registros-filho.

Este método utiliza duas datas como entrada, um DataAdapter para recuperar as mensagens postadas naquele intervalo e, em seguida, armazena-as em uma tabela DataSet denominada Messages, desta forma:

 

Public Function GetData(ByVal fromDate As Date, ByVal toDate As Date) _

    As DataSet

    Dim ds As New DataSet()

        ‘intervalo

    Dim da As New OleDbDataAdapter("SELECT * FROM Blog_Messages WHERE" & _

        "AddedDate BETWEEN ? AND ?" & _    

        "ORDER BY AddedDate DESC", m_Connection)

    da.SelectCommand.Parameters.Add("FromDate", OleDbType.Date).Value = _

        fromDate

    da.SelectCommand.Parameters.Add("ToDate", OleDbType.Date).Value = _

        toDate.AddDays(1)

    m_Connection.Open()

    da.Fill(ds, "Messages")

•••

 

Observe que você precisa adicionar um dia à data final para que as mensagens postadas naquela data sejam incluídas no resultset. Para obter apenas os comentários das mensagens retornadas, na instrução SELECT, use um filtro IN que contém uma lista delimitada por vírgulas de todas as IDs de mensagens dos registros retornados pela consulta exibida. Veja o seguinte código.

 

If ds.Tables("Messages").Rows.Count > 0 Then

    Dim inFilter As String = ""

    Dim dr As DataRow

    For Each dr In ds.Tables("Messages").Rows

        If inFilter = "" Then

            inFilter = dr("MessageID")

        Else

            inFilter &= ", " & dr("MessageID")

        End If

    Next

    ' Define o SELECT da tabela Comments

    da.SelectCommand.CommandText = "SELECT * FROM Blog_Comments" & _

        "WHERE MessageID IN (" & _

        "inFilter & ") ORDER BY AddedDate DESC"

Else

    da.SelectCommand.CommandText = "SELECT * FROM Blog_Comments" & _

        "WHERE MessageID = -1"

End If

da.Fill(ds, "Comments")

m_Connection.Close()

 

Em seguida, você cria o relacionamento entre as duas tabelas, desta forma:

 

ds.Relations.Add(New DataRelation("MsgComments", _

    ds.Tables("Messages").Columns("MessageID"), _

    ds.Tables("Comments").Columns("MessageID")))

 

Depois, você adiciona uma coluna calculada à tabela Messages que retorna o número de comentários-filho:

 

ds.Tables("Messages").Columns.Add("CommentsCount", _

    GetType(Integer), "Count(Child(MsgComments).CommentID)")

 

A função Child(MsgComments) retorna todas as linhas de dados-filho do relacionamento especificado, e a função Count funciona da mesma forma que no SQL.

O último detalhe a ser observado é que, antes de retornar o DataSet preenchido e terminar a função, se a tabela Comments estiver vazia, essa coluna calculada avaliará a expressão como NULL, e não 0, o que causará problemas mais tarde quando você mostrar esse valor na página ASP.NET ou usá-lo em outras expressões. Para solucionar esse problema, você pode adicionar um comentário falso, que não se refira à nenhuma mensagem:

 

If ds.Tables("Comments").Rows.Count = 0 Then

        Dim dr As DataRow = ds.Tables("Comments").NewRow()

        dr("CommentID") = -1

        dr("Author") = "none"

        dr("Email") = "none"

        dr("Comment") = "none"

        dr("AddedDate") = Date.Today

        ds.Tables("Comments").Rows.Add(dr)

        dr.AcceptChanges()

    End If

    Return ds

End Function

 

Dessa maneira, a expressão será avaliada como 0 se a mensagem não contiver nenhum comentário-filho.

 

Exibindo Mensagens e Comentários-Filho

Agora que a camada de negócio está completa, a próxima etapa será escrever a camada de apresentação, onde você poderá apreciar os recursos do ASP.NET. Grande parte do trabalho é realizado em uma única página, Default.aspx, que processará as mensagens e comentários e permitirá que o administrador modere os comentários e edite suas próprias mensagens (veja o painel inferior da Figura 2).

 

image002.gif

Figura 2 O Blog no Modo Administrator

 

A página Default.aspx mostra uma lista das mensagens agrupadas por dia; cada mensagem poderá ter vários ou nenhum comentário. Inicialmente, os comentários estarão ocultos, mas quando o usuário clicar no link View (Exibir), uma lista interna de comentários será dinamicamente expandida ou encolhida. É possível expandir várias listas de comentários de mensagens ao mesmo tempo, como se fosse um controle TreeView com um único nível de sub-nós. O desenvolvimento dessa página apresenta alguns desafios, tais como classificar as mensagens por dia, agrupá-las em tabelas HTML separadas e exibir ou ocultar as listas internas de registros-filho.

Vamos ao primeiro tópico. Suponha que você deseje exibir cinco mensagens na página; as primeiras três foram postadas no mesmo dia e as outras duas em outro dia. Suponha também que você deseje inicialmente manter cada mensagem em sua própria tabela HTML. Caso deseje agrupar as primeiras três mensagens e as duas mensagens remanescentes em duas tabelas separadas, você deveria remover as tags de abertura e fechamento da tabela das mensagens existentes no meio da tabela (as mensagens que não forem a primeira nem a última daquele dia), remover a tag de fechamento da tabela da primeira mensagem e a tag de abertura da tabela da última mensagem. Como usaremos um modelo de controle data-bound para processar essa exibição (view), e já que é necessário ter amplo controle sobre todo o HTML produzido, usaremos o controle Repeater. O controle Repeater não tem nenhuma saída ou layout pré-definido, além daqueles especificado em seus modelos. O código a seguir é uma definição parcial do modelo do controle Repeater.

 

<asp:repeater id="Blog" Runat="server">

    <HeaderTemplate><table width="100%" border="1"><tr><td>

    </HeaderTemplate>

    <ItemTemplate>

        <asp:Literal Runat="server" ID="DayBox">

            </td></tr></table><table width="100%" border="1"><tr><td>

        </asp:Literal>

        <asp:Panel Runat="server" ID="DayTitle">

            <h3><%# DataBinder.Eval(Container.DataItem, _

                "AddedDate", "{0:dddd, MMMM dd}") %></h3>

        </asp:Panel>

        <!—aqui está o restante do template para exibir os dados... -->

    </ItemTemplate>

    <FooterTemplate></td></tr></table></FooterTemplate>

</asp:repeater>

 

O HeaderTemplate contém as tags de abertura da tabela, e o FooterTemplate fecha a tabela. As tags que abrem e fecham as várias tabelas que agrupam as mensagens por data são definidas em um controle Literal dentro da seção ItemTemplate. Ao alternar a visibilidade do controle Literal, você decide se deseja mostrar essas tags e, desse modo, define quando deseja fechar a tabela atual e abrir uma nova. A questão é: quando ocultar o controle Literal para que a tabela atual seja mantida aberta e as mensagens agrupadas e quando exibi-la, para que a tabela seja fechada e uma nova tabela seja aberta.

A resposta é simples: as mensagens serão agrupadas enquanto a data da mensagem que está sendo processada for a mesma que a da mensagem anterior. Quando a data for alterada, o controle Literal será exibido e um novo grupo será iniciado. A outra questão é quando e onde o conteúdo do controle deverá estar visível. Você precisa decidir isso enquanto o item de dados (o registro da mensagem do blog) estiver sendo processado e vinculado ao modelo do Repeater no evento ItemDataBound do Repeater. Aqui você pode ler todos os valores do item de dados atual e compará-los aos dados da mensagem anterior, o qual foi armazenado em uma variável estática (Static prevDayDate) para que seu valor fosse preservado entre as chamadas de método. Você obtém uma referência ao controle Literal do modelo e pode definir sua visibilidade de acordo com ele. Aqui está o código:

 

Private Sub Blog_ItemDataBound(...) Handles Blog.ItemDataBound

    Static prevDayDate As Date

 

    If e.Item.ItemType <> ListItemType.Item AndAlso _

        e.Item.ItemType <> ListItemType.AlternatingItem Then Return

 

    Dim dayDate As Date = e.Item.DataItem("AddedDate")

    Dim isNewDay As Boolean = (dayDate.ToShortDateString() <> _

        prevDayDate.ToShortDateString())

    prevDayDate = dayDate

    CType(e.Item.FindControl("DayTitle"), Panel).Visible = isNewDay

    CType(e.Item.FindControl("DayBox"), Literal).Visible = ( _

        isNewDay AndAlso e.Item.ItemIndex > 0)

End Sub

 

Como você pode ver, a propriedade Visible está especificada para o controle Panel (também declarada no mesmo modelo) que exibe a data para o dia da tabela atual. Esse painel será mostrado se a data da mensagem atual diferir da data da mensagem anterior, ou ele mostrará a data padrão se esta for a primeira mensagem vinculada. A visibilidade do controle Literal terá mais uma restrição se for definida como True: a mensagem vinculada não poderá ser a primeira porque, nesse caso, a tabela do dia será aberta com as tags declaradas na seção HeaderTemplate do Repeater. Para encolher e expandir dinamicamente a lista de comentários sem que haja um postback para o servidor e o conseqüente reprocessamento da página, inclua os comentários dentro de uma tag <DIV> cujo estilo de exibição possa ser definido como "none" ou como uma seqüência de caracteres vazia, para ocultá-la ou exibi-la, respectivamente. A DIV é declarada da seguinte forma, atribuindo-se a ela um ID baseado no item de dados que está sendo vinculado:

 

<div

    ID='<%# "div" & Container.DataItem("MessageID") %>'>

    <!—coloque aqui os comentários do DataList -->

</div>

 

O ID foi atribuído dessa maneira para que se tornasse exclusivo de cada DIV (lembre-se de que existe uma para cada mensagem). Para expandir ou encolher a DIV, foi usado um hyperlink que chama o código JavaScript personalizado (custom JavaScript) que recebe as informações do ID da DIV:

 

<asp:HyperLink Runat="server"

    Visible='<%# Container.DataItem("CommentsCount") > 0 %>'

    NavigateUrl='<%# "javascript:ToggleDivState(div" & _

       Container.DataItem("MessageID") & ");" %>'>

    View

</asp:HyperLink>

 

Observe também que a propriedade Visible é vinculada a uma expressão que retornará True apenas se a mensagem tiver comentários para mostrar (se o valor em sua coluna CommentsCount calculada for maior que 0). O ToggleDivState apenas inverte o valor do estilo de exibição da DIV para fazer com que ele alterne entre o modo visível ou oculto:

 

function ToggleDivState(ctrl)

{

    div = eval(ctrl);

    if (div.style.display == "none")

        div.style.display = "";

    else

        div.style.display = "none";

}

 

Agora, vamos analisar a apresentação dos comentários. O DataList fará o trabalho agora porque o layout da tabela é exatamente o que precisamos. Geralmente, é atribuída dinamicamente a propriedade DataSource de um controle (ou qualquer outro controle de lista de data-bound) no codebehind (ou em uma seção de script no lado servidor). Neste caso, porém, não há uma referência direta ao DataList porque ele foi criado dinamicamente pelo Repeater-pai. No entanto, a exemplo de muitas outras propriedades, ele pode ter uma expressão data binding que defina seu valor em tempo de execução. Se você estava usando a abordagem DataReader para recuperar os dados, a propriedade DataSource poderia ser vinculada a um método personalizado que recebesse a ID da mensagem e retornasse os comentários-filho em um DataTable, DataReader ou em qualquer outro tipo de dado que implementasse a interface IEnumerable. Neste exemplo, isso não é necessário porque todos os dados de que você precisa já estão armazenados no mesmo DataSet que contém as mensagens. Como você criou um relacionamento entre as tabelas (mensagem e comentários), é possível recuperar facilmente um array com os comentários-filho, usando o método GetChildRows do DataRow do item de dados atual. A expressão é declarada desta forma:

 

<asp:DataList Runat="server" DataSource=

'<%# Container.DataItem.Row.GetChildRows("MsgComments") %>'>

 

Complete o ItemTemplate do DataList com as expressões para mostrar o nome e o endereço de e-mail do autor, o texto da mensagem e a data; e então, o código que gera o conteúdo do blog estará completo. A Listagem 1 mostra o código finalizado para o módulo gerado.

 

Listagem 1 – Código completo do Repeater

<asp:repeater id="Blog" Runat="server">

    <HeaderTemplate>

        <table width="100%" border="1" cellpadding="0" cellspacing="0">

        <tr><td>

    </HeaderTemplate>

    <ItemTemplate>

        <asp:Literal Runat="server" ID="DayBox">

            </td></tr></table><div align="right"><a href="#TopPage">Top

            </a></div><br>

            <table width="100%" border="1" cellpadding="0"

             cellspacing="0"><tr><td>

        </asp:Literal>

        <asp:Panel Runat="server" ID="DayTitle">

            <h3><%# DataBinder.Eval(Container.DataItem, "AddedDate",

                 "{0:dddd, MMMM dd}") %></h3>

        </asp:Panel>

 

        <p align="justify">

            <b><%# Container.DataItem("Title") %></b>

            @ <%# DataBinder.Eval(Container.DataItem, "AddedDate",

               "{0:hh:mm tt}") %>

            <br>

            <%# Container.DataItem("Message") %>

            <br>

            -- <%# Container.DataItem("CommentsCount") %>

            comments:

            <asp:HyperLink Runat="server" Visible='

                <%# Container.DataItem("CommentsCount") > 0 %>'

                NavigateUrl='<%# "javascript:ToggleDivState(div" &

                Container.DataItem("MessageID") & ");" %>'>

                View</asp:HyperLink>

            - <a href='<%# "javascript:ShowCommentBox(" &

              Container.DataItem("MessageID") & ");" %>'>

                Post your own comment</a>

            <div style="display:'none'; margin-left:2.0em;

                margin-top:.8em; "

                ID='<%# "div" & Container.DataItem("MessageID") %>'>

                <asp:DataList Runat="server" Width="500px"

                    ItemStyle-BackColor="whitesmoke"

                    AlternatingItemStyle-BackColor="white"

                    DataSource='<%# Container.DataItem.Row.GetChildRows

                    ("MsgComments") %>'

                    OnDeleteCommand="Comments_DeleteCommand"

                    OnItemCreated="Comments_ItemCreated"

                    OnEditCommand="Comments_EditCommand">

                    <ItemTemplate>

                        <p align="justify">

                            <u>Posted by <a class="comment"

                               href='<%# "mailto:" &

                               Container.DataItem("Email") %>'>

                            <%# Container.DataItem("Author") %></a></b>

                            @ <%# CType(Container.DataItem("AddedDate"),

                               Date).ToString("hh:mm tt, MMMM dd") %>

                            </u>

                            <br>

                            <%# Container.DataItem("Comment") %>

                        </p>

                    </ItemTemplate>

                    <SeparatorTemplate><br></SeparatorTemplate>

                </asp:DataList>

            </div>

        </p>

    </ItemTemplate>

    <FooterTemplate>

        </td></tr></table><div align="right"><a href="#TopPage">Top</a>

        </div><br>

    </FooterTemplate>

</asp:repeater>

 

Este artigo foi a primeira parte que ensina como criar um blog. Na segunda parte você verá como selecionar o blog em um período de datas, trabalhar com janelas de calendário pop-up, inserir comentários e administrar o blog.