WebMatrix - Gerenciando Usuários, Pedidos e Categorias - 9


Na  oitava parte deste artigo iniciamos a implementação da área de administração do site vamos continuar criando um link na página principal para acessar a área de administração e criar as tarefas do administrador.

Obs: Enquanto eu escrevia esta série de artigos foi liberada a versão 2 do WebMatrix e a área de administração vai ser criada usando esta versão.

Para baixar a nova versão clique no link: http://www.microsoft.com/web/webmatrix/

Criando o link para a área de administração - Ajustando o Helper ResumoContas

Embora possamos acessar a área de administração do site navegando pela URL /Admin seria mais adequado fornecer um link após o login na página do site de forma que o acesso fosse feito apenas clicando neste link.O processo administrativo que iremos definir em nosso site incluem:

Para fazer isso vamos alterar o Helper ResumoConta() do arquivo MembershipHelpers.cshtml que esta na pasta App_Code e incluir uma nova seção para exibir no canto superior de cada página o link para a área de administração.

Abra o arquivo App_Code/MembershipHelpers.cshtml e inclua o código destacado conforme mostra a figura abaixo:

Este código exibirá um hiperlink adicional com o nome Admin para a área de administração do site para os usuários que após se autenticarem forem membros do grupo de administradores.

Gerenciando os usuários administradores

Agora que temos a infraestrutura para a área de segurança de administração do nosso site, vamos precisar de uma maneira de adicionar e remover usuários existentes com perfil de administrador.

Vamos criar uma nova pasta chamada Users dentro da Pasta Admin e dentro dela vamos criar uma página Default.cshtml que irá listar todos os membros atuais com função de administrador com um botão para remoção do usuário e também com uma opção de incluir novos membros.

Clique com  botão direito sobre a pasta Admin e selecione Nova Pasta (New Folder) e informe o nome Users.

A seguir Clique com  botão direito sobre a pasta Users e selecione Novo Arquivo (New File), selecione o template CSHTML e informe o nome Default.cshtml e clique no botão OK.

Agora abra o arquivo Default.cshtml e substitua o código existente pelo código abaixo:

@{
  Layout = "~/Shared/Layouts/_AdminLayout.cshtml";
  Page.Title = "Usuários Administradores";
  var usuarioAIncuir = "";
  var role = "Admin";
  if(IsPost)
  {
      usuarioAIncuir = Request["usuarioAIncuir"];
      if(usuarioAIncuir != null)
      {
        // Incuir o usuário no perfil admin
        if (WebSecurity.UserExists(usuarioAIncuir) && !Roles.IsUserInRole(usuarioAIncuir, role))
        {
          Roles.AddUsersToRoles(new [] { usuarioAIncuir }.ToArray(),new [] { role }.ToArray());
        }
        else
        {
          ModelState.AddError("usuarioAIncuir", "Não foi possível incluir usuário como Admin");
        }
     }
     else if (Request["usuarioARemover"] != null)
     {
       // Remove usuário
       var usuarioARemover = Request["usuarioARemover"];
       if (Roles.IsUserInRole(usuarioARemover, role))
       {
          Roles.RemoveUsersFromRoles(new [] { usuarioARemover }.ToArray(),new [] { role }.ToArray());
       }
     }
  }
  var users = Roles.GetUsersInRole(role);
}
 <h1>Usuários Administradores</h1>
 <table class="grid">
 <tr>
   <th colspan="3" class="gridHeader">Usuário</th>
 </tr>
  @foreach (var user in users)
  {
   <tr>
    <td id="mainColumn">@user</td>
    <td>
     @{
       var buttonState = "";
       if(user == WebSecurity.CurrentUserName)
       {
         buttonState = "disabled=disabled";
       }
     }
     <form action="" method="post">
       @Html.Hidden("usuarioARemover", user)
       <input type="submit" value="Remover" @buttonState />
     </form>
    </td>
   </tr>
  }
 </table>
<h2>Incluir Usuário</h2>
<form action="" method="post">
  <p>
    Usuário: @Html.TextBox("usuarioAIncuir", usuarioAIncuir)
    <input type="submit" value="Incluir" />
    @Html.ValidationMessage("usuarioAIncuir")
  </p>
</form>
 

O manipulador de solicitação POST verifica se um campo usuarioAIncuir foi passado nos valores do formulário postado. Se ele for passado, nós sabemos que o usuário submeteu o formulário 'Adicionar usuário Admin', então tentamos adicionar  o usuário no perfil admin.

Caso contrário, se um campo usuarioARemover for apresentado, sabemos que o usuário clicou no botão "Remover', para que tente remover o usuário. Aqui usamos um mecanismo semelhante ao usado na página do Carrinho de Compras.

A página Default.cshtml com o código acima deverá ter a seguinte aparência:

Note que não permitimos que usuário atualmente logado seja removido, fazemos isso desabilitando o botão remover para este usuário.

Para incluir um novo usuário exigimos que seja digitado o nome do usuário da conta que desejamos adicionar ao perfil de administrador, poderíamos ter fornecido uma lista suspensa de usuários registrados mas por uma questão de desempenho preferimos não fazê-lo.

Criando a página de Administração dos pedidos

Vamos precisar de uma área em nosso site, onde podemos ver os pedidos dos clientes e onde poderemos marcá-los como enviados. O processo de administração dos pedidos será composto por duas páginas.

A primeira página irá listar um resumo de todas os pedidos em um WebGrid. Cada linha da WebGrid irá exibir um hiperlink para levar o usuário para a segunda página, que
exibe os detalhes completos do pedido.

Vamos começar criando a página de resumo dos pedidos.

Clique com  botão direito sobre a pasta Admin e selecione Nova Pasta (New Folder) e informe o nome Pedidos.

A seguir Clique com  botão direito sobre a pasta Pedidos e selecione Novo Arquivo (New File), selecione o template CSHTML e informe o nome Default.cshtml e clique no botão OK.

Agora abra o arquivo Default.cshtml e substitua o código existente pelo código abaixo:

@{
  Layout = "~/Shared/Layouts/_AdminLayout.cshtml";
  Page.Title = "Pedidos";
  var exibirPedidosEnviados = false;
  if(IsPost)
  {
    exibirPedidosEnviados = Request["exibirPedidosEnviados"].AsBool();
  }
  var db = Database.Open("TecnoSite");
  var sqlCommand = "SELECT Pedidos.pedidoid, Pedidos.pedidoEnviado, Pedidos.pedidoTotal, " +
                             "Pedidos.pedidoDataHora, UserProfile.Email FROM Pedidos " +
                            "INNER JOIN UserProfile ON Pedidos.usuarioid = UserProfile.UserId " +
                            "WHERE pedidoEnviado = @0 " +
                            "ORDER BY pedidoDataHora DESC";
  var resultado = db.Query(sqlCommand, exibirPedidosEnviados);
  var pedidosGrid = new WebGrid(source: resultado, rowsPerPage: 20);
}
<h1>Resumo dos Pedidos</h1>
 <form action="Default" method="post">
  <p>
    Pedidos não Enviados @Html.RadioButton("exibirPedidosEnviados", "false", !exibirPedidosEnviados)
    Pedidos Enviados @Html.RadioButton("exibirPedidosEnviados", "true", exibirPedidosEnviados)
   <input type="submit" value="Visualizar"/>
 </p>
</form>
<div class="grid">
@pedidosGrid.GetHtml(
                   tableStyle: "grid",headerStyle: "gridHeader",alternatingRowStyle: "gridAlt",footerStyle: "gridFooter",
                   columns: pedidosGrid.Columns(pedidosGrid.Column("pedidoid","No. Pedido"),
                   pedidosGrid.Column("Email","Cliente",format:  @<text><a href="mailto:@item.Email">@item.Email</a></text>),
                   pedidosGrid.Column("pedidoEnviado","Enviado?"),
                   pedidosGrid.Column("pedidoTotal","Valor Total",
                   format: @<text> R$@item.pedidoTotal</text>),
                   pedidosGrid.Column("pedidoDataHora","Data Pedido"),
                   pedidosGrid.Column(null,null,format: @<text> <a href="PedidosDetalhes/@item.pedidoid">Detalhes</a></text>)))
</div>

Neste código estamos usando o Helper WebGrid para exibir um resumo dos pedidos, dessa forma podemos incluir a habilidade de paginação e ordenação no grid.

Também incluímos um formulário que permite ao administrador escolher se deseja visualizar os pedidos enviados ou os não enviados.

Abaixo vemos o resultado da página Default.cshtml da pasta /Admin/Pedidos:

Olhando o código, você pode ver que a instrução SQL para obter os dados para o WebGrid contém um INNER JOIN entre as tabelas Pedidos e UserProfile. Isso nos permite recuperar o nome do usuário (e-mail) do cliente, com base na UserId armazenado em cada pedido.

A cláusula WHERE da instrução SQL filtra os resultados com base na coluna pedidoEnviado  que por padrão é definido como False.  O corpo da página contém o nosso helper WebGrid, que é preenchido a partir dos resultados a consulta SQL

A grade é simples, exceto que a última coluna não é associado a uma coluna de dados, mas exibe um hiperlink para a página de detalhes do pedido, passando o pedidoid na URL.

Criando a página de detalhes do pedido

A página Detalhes dos pedidos exibe os detalhes completos de qualquer pedido especifico, incluindo a data e hora do pedido, detalhes de despacho, detalhes de todos os itens dentro do pedido e status de envio.

A página inclui um checkbox
"pedido enviado? , que o administrador pode marcar para indicar o pedido foi despachado. Isto irá definir o campo pedidoEnviado como True, o qual irá colocar o pedido na lista de pedidos enviados na lista de resumo dos pedidos.

Vamos criar uma nova página na pasta
/Admin/Pedidos/ chamada PedidoDetalhes.cshtml e incluir o código abaixo neste arquivo: 

@{
  Layout = "~/Shared/Layouts/_AdminLayout.cshtml";
  Page.Title = "Detalhes do Pedido";
  // Pega o pedidoid da URL, ou define para 0 se não localizada
  var pedidoid = !UrlData[0].IsEmpty() ? UrlData[0] : "0";
  var db = Database.Open("TecnoSite");
  if (IsPost)
  {
    pedidoid = Request["pedidoid"];
    var enviado = Request["enviado"].AsBool();
    var sqlUpdate = "UPDATE Pedidos SET pedidoEnviado = @0 WHERE pedidoid = @1";
    db.Execute(sqlUpdate, enviado, pedidoid);
    Response.Redirect("~/Admin/Pedidos/");
  }
  // Pega cabeçaho dos detalhes
  var sqlCommand = "SELECT Pedidos.*, UserProfile.Email FROM Pedidos " +
                             "INNER JOIN UserProfile ON Pedidos.usuarioid = UserProfile.UserId " +
                             "WHERE pedidoid = @0 ";
  var _pedido = db.QuerySingle(sqlCommand, pedidoid);
  // Pega os itens dos pedidos
  var itemsSql = "SELECT * FROM PedidoItens WHERE pedidoid = @0";
  var pedidoItens = db.Query(itemsSql, pedidoid);
}
  @if (_pedido == null)
  {
    <p>Não foi possível obter detalhes do pedido.</p>
  }
  else
  {
  <text><h1>Pedido</h1>
    <p><strong>ID do Pedido:</strong> @_pedido.pedidoid</p>
    <p><strong>Cliente:</strong> <a href="mailto:@_pedido.Email">@_pedido.Email</a></p>
    <p><strong>Data Pedido: </strong>@_pedido.pedidoDataHora</p>
    <h2>Despacho</h2>
    <p><strong>Endereço:</strong> @_pedido.endereco</p>
    <p><strong>Cidade: </strong>@_pedido.cidade</p>
    <p><strong>Estado:</strong> @_pedido.estado</p>
    <p><strong>Cep:</strong> @_pedido.cep</p>
    <p><strong>País: </strong>@_pedido.pais</p>
      <form action="PedidoDetalhes" method="post" id="shippedForm">
       @Html.Hidden("pedidoid", _pedido.pedidoid)
       Pedido Enviado?
       @Html.CheckBox("shipped", _pedido.pedidoEnviado, new { value = "true" })
       <input type="submit" value="Atualizar" />
     </form>
    <h2>Itens do Pedido</h2>
    <table id="cartTable" border="0">
    <tr>
      <th class="product">Produto</th>
      <th class="price">Preço</th>
    </tr>
    @foreach (var item in pedidoItens)
    {
      <tr>
        <td class="product">@Produtos.GetNomeProdutoPorID(item.produtoid)</td>
        <td class="price">R$@item.preco</td>
      </tr>
    }
    <tr class="cartTotal">
    <td>Valor Total: </td>
    <td>R$@_pedido.pedidoTotal</td>
    </tr>
   </table>
 </text>
}
 

Quando a página inicialmente carrega, o código do pedido - pedidoid - é recuperado da URL e duas consultas de banco de dados são executadas.
A primeira consulta preenche a metade superior da página, incluindo a seção Detalhes do despacho, a segunda consulta recupera todos os itens individuais que fazem parte do pedido sendo exibidos na seção Itens do Pedido.

Se o usuário marcar a caixa de seleção "Pedido Enviado?" e clicar no botão "Atualizar", o valor do do campo pedidoEnviado no banco de dados será atualizado para refletir o valor da caixa de seleção e o usuário é retornado para a página Resumo dos pedidos.

Ao realizar essa ação, você vai notar que o pedido agora é listado na relação dos pedidos enviados da página Resumo dos pedidos.

Abaixo vemos a página PedidosDetalhes.cshtml sendo exibida:

Definindo a página Categorias dos produtos

Os produtos do catálogo do TecnoSite são organizados em categorias. Todos os produtos devem ser atribuídos a uma categoria.

As categorias de produtos são armazenados na tabela de Categorias da nossa base de dados e são usados para construir dinamicamente o menu Shared/Partials/_Categorias.cshtml visto no lado esquerdo de cada página do site.

Precisamos uma forma de adicionar, editar e excluir itens da tabela Categorias, para que possamos manter o catálogo de produtos bem organizado. Portanto, adicione uma nova subpasta chamada Categorias dentro da pasta Admin e dentro dela vamos criar uma nova página chamada Default.cshtml.

Esta página será usado para listar todas as categorias da tabela e permitirá que os administradores adicionem uma nova categoria.

Clique com  botão direito sobre a pasta Admin e selecione Nova Pasta (New Folder) e informe o nome Categorias.

A seguir Clique com  botão direito sobre a pasta Categorias e selecione Novo Arquivo (New File), selecione o template CSHTML e informe o nome Default.cshtml e clique no botão OK;

A seguir inclua o código abaixo neste arquivo substituindo o código gerado por padrão:

@{
Layout = "~/Shared/Layouts/_AdminLayout.cshtml";
  Page.Title = "Categorias";
  var categoriaTitulo = "";
  var db = Database.Open("TecnoSite");
  if(IsPost && !Request["categoriaTitulo"].IsEmpty())
  {
       categoriaTitulo = Request["categoriaTitulo"];
       if(!Admin.CategoriaExiste(categoriaTitulo))
       {
         var sqlInsert = "INSERT INTO Categorias (categoriaTitulo) VALUES (@0)";
         db.Execute(sqlInsert, categoriaTitulo);
       }
       else
       {
            ModelState.AddError("categoriaTitulo", "Categoryia já exsite");
        }
  }
  var sqlCommand = "SELECT * FROM Categorias";
  var _categorias = db.Query(sqlCommand);
}
<h1>Categorias</h1>
<table class="grid">
<tr>
   <th colspan="3" class="gridHeader">Categoria</th>
</tr>
@foreach (var categoria in _categorias)
{
  <tr>
    <td id="mainColumn">@categoria.categoriaTitulo</td>
    <td><a href="EditaCategoria/@categoria.categoriaid">Edita</a></td>
    <td><a href="DeletaCategoria/@categoria.categoriaid">Deleta</a></td>
  </tr>
}
</table>
<h2>Nova Categoria</h2>
<form action="" method="post">
  <p>Título da Categoria: @Html.TextBox("categoriaTitulo", categoriaTitulo)
     <input type="submit" value="Incluir" />
     @Html.ValidationMessage("categoriaTitulo")
  </p>
</form>
 

O resultado desta será a exibição da seguinte página:


Em sua carga inicial, a página recupera todas as categorias do banco de dados e as apresenta em uma tabela, com dois links ao lado de cada categoria para enviar o usuário para as páginas Editar e Excluir.

Embaixo da lista de categorias, um formulário HTML é exibido para permitir ao usuário adicionar um novo item na tabela Categorias.

Quando este formulário é enviado de volta para o servidor, o método Admin.CategoriaExiste() será chamado para garantir que nenhuma categoria duplicada será criada.

Se o método retornar false, o registro é inserido na tabela Categorias e a página exibida novamente. Se a categoria já existe, um erro é adicionado ao ModelStateDictionary, a ser exibida para o usuário através de uma chamada ao método Helper Html.ValidationMessage() na seção Nova Categoria.

Admin.CategoriaExiste() é uma função razor personalizada que iremos criar a seguir. Estamos criando como uma função, ao invés de apenas adicionar o código diretamente na página, pois vamos usar este método novamente para realizar a edição das categorias.

Adicionar um novo arquivo chamado Admin.cshtml dentro da pasta do App_Code.

A seguir inclua o código abaixo neste arquivo:

@* Admin Funcões *@
@functions {
  public static bool CategoriaExiste(string categoriaTitulo)
  {
   var db = Database.Open("TecnoSite");
   var sqlQuery = "SELECT categoriaid FROM Categorias WHERE categoriaTitulo = @0";
   return db.Query(sqlQuery, categoriaTitulo).Count() > 0;
  }
}

O método CategoriaExiste() aceita um parâmetro string chamado categoriaTitulo e retorna um valor boleano (True/False). O valor do parâmetro é procurado no banco de dados e se nenhuma linha for encontrada o resultado retornará false.

Editando Categorias

A página para editar Categorias deverá exibir uma caixa de texto permitindo que um administrador altere o título da categoria. O código no tratamento da requisição POST é parecido com o usado na página das Categorias.

Quando o formulário for postado de volta vamos chamar a função Razor Admin.CategoriaExiste() para ter certeza de não estarmos duplicando uma categoria já criada, antes de submeter a atualização ao banco de dados.

Vamos incluir um novo arquivo na pasta Admin/Categorias chamado EditaCategoria.cshtml.

A seguir Clique com  botão direito sobre a pasta Categorias e selecione Novo Arquivo (New File), selecione o template CSHTML e informe o nome EditaCategoria.cshtml e clique no botão OK;

A seguir substitua o código gerado pelo código abaixo:

@{
  Layout = "~/Shared/Layouts/_AdminLayout.cshtml";
  Page.Title = "Edita Categoria";
  // Pega a categoriaid daf URL, ou define para zero se não existir
  var categoriaid = !UrlData[0].IsEmpty() ? UrlData[0] : "0";
  var categoriaTitulo = "";
  var db = Database.Open("TecnoSite");
  if(IsPost)
  {
     categoriaid = Request["categoriaid"];
     categoriaTitulo = Request["categoriaTitulo"];
     // Validação
     if (categoriaTitulo.IsEmpty())
     {
        ModelState.AddError("categoriaTitulo", "O título da Categoria não pode ser vazio");
     }
     if(Admin.CategoriaExiste(categoriaTitulo))
     {
        ModelState.AddError("categoriaTitulo", "Esta Categoria já existe");
     }
     if(ModelState.IsValid)
     {
        var sqlUpdate = "UPDATE Categorias SET categoriaTitulo = @0 WHERE categoriaid = @1";
        db.Execute(sqlUpdate, categoriaTitulo, categoriaid);
        Response.Redirect("~/Admin/Categorias/");
     }
  }
  else
  {
     var sqlCommand = "SELECT * FROM Categorias WHERE categoriaid = @0";
     categoriaTitulo = db.QuerySingle(sqlCommand, categoriaid).categoriaTitulo;
   }
}
<h1>Edita Categoria</h1>
<form action="" method="post">
   <p>
   Título da Categoria: @Html.Hidden("categoriaid", categoriaid)
   @Html.TextBox("categoriaTitulo", categoriaTitulo)
   <input type="submit" value="Atualizar" />
   @Html.ValidationMessage("categoriaTitulo")
   </p>
</form>

Executando o projeto novamente, efetuando o login com perfil Admin e editando uma categoria iremos obter a seguinte página:

Deletando Categorias

Quando formos deletar uma categoria temos que tomar cuidado para não deletar uma categoria que possua produtos associado a ela.

O código desta página é muito simples e chama uma função helper para verificar se a categoria esta vazia antes de prosseguir com o processo de exclusão.

A exclusão é executada usando o código da categoria, categoriaid, passada na URL a partir da página das categorias.(Default.chstml)

Ao acessar a página para deletar uma categoria e se a categoria estiver vazia será apresentada uma mensagem se confirmação e dois botões.

Um dos botões é o botão - Deleta - que submete o formulário (ou seja processa a deleção) que é processada pelo manipulador de requisição POST no código da página.

O segundo botão é o botão - Cancela - o qual usa o código JavaScript no atributo OnClick para enviar o usuário diretamente para a página das categorias (Default.cshtml) sem submeter o formulário.

Se o usuário admin acessar a página e a categoria não estiver vazia, uma mensagem será exibida ao usuário informando que a categoria não pode ser deletada pois possui produtos associados a ela.

Vamos incluir um novo arquivo na pasta Admin/Categorias chamado DeletaCategoria.cshtml.

A seguir Clique com  botão direito sobre a pasta Categorias e selecione Novo Arquivo (New File), selecione o template CSHTML e informe o nome DeletaCategoria.cshtml e clique no botão OK;

A seguir substitua o código gerado pelo código abaixo:

@{
  Layout = "~/Shared/Layouts/_AdminLayout.cshtml";
  Page.Title = "Deleta Categoria";
  var categoriaid = UrlData[0];
  if (categoriaid.IsEmpty()) {
     Response.Redirect("~/Admin/Categorias/");
  }
  var db = Database.Open("TecnoSite");
  if (IsPost)
  {
     var sqlDeleta = "DELETE FROM Categorias WHERE categoriaid = @0";
     db.Execute(sqlDeleta, categoriaid);
     Response.Redirect("~/Admin/Categorias/");
  }
  var sqlSeleciona = "SELECT * FROM Categorias WHERE categoriaid = @0";
  var categoria = db.QuerySingle(sqlSeleciona, categoriaid);
}
<h1>Deleta Categoria</h1>
 @if (!Admin.CategoriaVazia(categoriaid))
 {
   <p>Não foi possível deletar a categoria @categoria.CategoriaTitulo pois ela possui produtos associados.</p>
 }
 else
 {
  <p>Confirma a exclusão da categoria : @categoria.CategoriaTitulo ?</p>
  <p style="margin:">
   <form action="" method="post" id="deleteForm">
     <input type="button" onclick="window.location = '@Href("~/Admin/Categorias/")';" value="Cancela" />
     <input type="submit" value="Deleta" />
   </form>
  </p>
}

Esta pagina esta chamando uma função Razor chamada CategoriaVazia que verifica se a categoria não possui produtos associados antes de concluir a exclusão.

Temos que criar esta função no arquivo Admin.cshtml presente na pasta App_Code.

Abra o arquivo Admin.cshtml e inclua a função CategoriaVazia conforme o código destacado em azul abaixo:

@* Admin Funcões *@
@functions
 {
    public static bool CategoriaExiste(string categoriaTitulo)
    {
       var db = Database.Open("TecnoSite");
       var sqlQuery = "SELECT categoriaid FROM Categorias WHERE categoriaTitulo = @0";
       return db.Query(sqlQuery, categoriaTitulo).Count() > 0;
    }
    public static bool CategoriaVazia(string categoriaId)
    {
       var db = Database.Open("TecnoSite");
       var sqlQuery = "SELECT produtoid FROM Produtos WHERE categoria = @0";
       return db.Query(sqlQuery, categoriaId).Count() == 0;
    }
}

Executando o projeto novamente e navegado até a página para gerenciar categorias podemos ter os seguintes resultados:

1- Se a categoria estiver vazia :

2- Se a categoria tiver produtos associados:

Aguarde a continuação da área de administração onde iremos implementar a administração dos produtos : WebMatrix -  Gerenciando Produtos - 10

Referências:


José Carlos Macoratti