Escolha uma Página
Como hospedar sua aplicação Asp.Net Core MVC (inclusive 2.2) fora do Azure

Como hospedar sua aplicação Asp.Net Core MVC (inclusive 2.2) fora do Azure

Como hospedar sua aplicação Asp.Net Core MVC (inclusive 2.2) fora do Azure

Por menos de U$3/mês você pode ter sua aplicação ASP.NET Core MVC 2.2 rodando num provedor realmente especializado em ASP.NET, com direito a e-mail, banco de dados MS SQL e um infinidade de recursos.

Usando Visual Studio 2017 versão comunitária (grátis) , ou também a versão comunitária do Visual Studio 2019, também grátis,  você consegue instalar sua aplicação ASP.NET Core 2.2 MVC num provedor especializado e com alta velocidade – e com um custo muito baixo. Esqueça Godaddy, LOCAWEB, e mesmo Azure (que acaba custando uma tinta quanto mais você usa).

Depois de muito pesquisar e apanhar, acabei descobrindo o provedor SmarterASP.NET..Em menos de uma hora, estava com minha aplicação Core 2.2 rodando. Aqui vai um passo a passo com dicas importantes. Detalhe importante: se você duvidar, pode ter 60 dias de uso grátis!!! Ou seja, você pode comprovar tudo o que estou falando aqui sem gastar um único centavo. Se escolher um plano pago você pode usar qualquer cartão de crédito (ou pagar com PAY-PAL). Se não gostar eles devolvem seu dinheiro.

Passo 1 – Incrição

Vá em SmarterASP.NET. NÃO se preocupe, este é o único formulário em inglês. Todo seu painel pode ser usado em português.

Escolha o plano 60 dias grátis, se não quiser pagar nada agora, Em segundos você recebe um e-mail de confirmação, podendo já acessar e publicar sua aplicação CORE MVC.

A primeira grata surpresa: você ganha um domínio prontinho, seu, para testar sua aplicação na WEB (depois você aponta um domínio seu, não se preocupe agora).

Passo 2 – Acesso

Clique no link recebido por e-mail enviado pela SmarterASP.NET. . Você já pode acessar seu painel. Escilha Português se não quiser inglês. Ao acessar, se o sistema perguntar onde quer seu servidor (USA ou Europa), defina onde quer. Eu defini USA e roda rapisíssimo, fiquei impressionado! Você vai ter de cara um domínio tipo seunomedeusuario-001-site1.htempurl.com


Passo 3 – Pegue os dados de Deploy para seu Visual Studio

Você não vai precisar usar FTP. Vai subir sua aplicação via Visual Studio e comprovar que é rapidíssimo comparada a qualquer subida via FTP. Clique em Sites e depois em Mostrar Informações Web Deploy. Não se preocupe em copiar nada.dados para visual studio

Clicando e Mostrar informações abre um painel abaixo:

Deixe VS Estado como ON (verde). Clique no botão “Se quiser publicar configuração”. Os lugares que risquei em amarelo vao ter seu nome de usuário.

Salve em seu disco local o arquivo Site1. Este é um arquivo com extensão .PubblishSettings, próprio para o Visual Studio. Seu conteúdo é este:

Aquivo para publish - SITE1
<publishData>
   <publishProfile 
   publishUrl="https://seunomedeusuario-001-site1.htempurl.com:8172/msdeploy.axd?site=seunomedeusuario-001-site1" 
   msdeploySite="seunomedeusuario-001-site1" 
   destinationAppUrl="http://seunomedeusuario-001-site1.htempurl.com/" 
   profileName="Default Settings" 
   publishMethod="MSDeploy" userName="ifcseunomedeusuario-001" 
   AllowUntrustedCertificate="True"
   /> 
</publishData>

Passo 4 – Abra sua aplicação no Visual Studio e instale o arquivo para PublishSettings

Clique no Solution Explorer do Visual Studio, no lado direito, no nome do seu projeto (com o botão direito). Escolha a opção Publishing.

PUBLISH


Clique em Create new profile:

create new profile


Clique em Import Profile e dê OK

import profile


Escolha o Arquivo Site1 que foi gravado no seu disco local:

arquivo site1

Clique no botão Publish. O Visual Studio iniciará a subida da sua aplicação, que você notará que é bem rápida.

publish

Uma dica importante: na primeira vez, ANTES de clicar no botão PUBLISH NO VISUAL STUDIO, CLIQUE EM settings, que fica no lado direito do painel de publicação (as vez fica meio escondido na tela). Clique em VALIDAR CONEXÃO (validate connection). Se o sistema reclamar alguma coisa de certificado, diz que sim, que aceita o certificado e pronto. Clique em SALVAR (save) e agora sim, pode publicar.

As vezes o Visual Studio “esquece” que você já fez isso e começa a dar erro na publicação do site. É só repetir a operação acima e tudo volta ao normal. A partir da segunda vez que você publica a atualização é rapidíssima! É a vantagem desse provedor permitir WEB DEPLOY, que só atualiza os arquivos que efetivamente mudaram… Se for fazer pelo FTP do Visual Studio, que é burrinho, pode sair e voltar um bom tempo depois (pois ele sobe todos os arquivos de novo, sempre). É melhor – neste caso, publicar num diretório e subir com o Filezilla, parametrizando-o para só enviar arquivos com novas datas. Mas certamente você vai esquecer que existe FTP usando o WEBDEPLOY…

Outras vantagens:

Você pode criar um número ilimitado de bancos de dados, inclusive SQL. Não paga nada a mais por isso. Para as minhas aplicações isso é muito bom. O desempenho dos servidores é ótimo.

Se tiver algum problema:

  • O provedor tem uma base de conhecimento com uma série de bons artigos explicativos, agrupados por tópicos. Tem tudo ali que em geral os clientes podem ter dúvidas;
  • Escreva para o suporte. Testei e me responderam rapidinho (nem acreditei, estou acostumado com o padrão 24 horas da Locaweb). O suporte é mesmo 24 x 7! Basta abrir um tíquete de suporte na Central de Ajuda.

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Usando SQL Storage Procedures no CORE MVC – parte 2

Usando SQL Storage Procedures no CORE MVC – parte 2

Usando SQL Storage Procedures no CORE MVC – parte 2

Um dos problemas complicados no Core MVC é que não dá para usar as Views do SQL. Quando você tem de mostrar dados que tem diversas tabelas agrupadas, há algumas formas de contornar isso, usando procedures do SQL.

Se você vai operar com procedures, antes de tudo instale pelo Gerenciador do NuGet – se ainda não o tem instalado na sua aplicação, o Microsoft.AspNetCore.Identity.EntityFrameworkCore.

Casos do método FromSql

Para executar procedures, use o método FromSql executa consultas RAW SQL. Mas existem algumas limitações para executar consultas RAW SQL ou procedures.

  • As consultas SQL só podem ser usadas para retornar tipos de entidades que fazem já parte do seu modelo. Portanto, não podem conter dados relacionados ou “montados” através de procedures. Existe uma forma de contornar isso, mas ninguém explica direito. Você deve montar um modelo “fake”, falso mesmo. Que não existe fisicamente no banco de dados. Depois de montar o modelo tem que fazer duas coisas:
    • incluir esse modelo “fake” no seu arquivo SuaAplicacaoContext.cs, dentro do diretório de modelos (Models);
    • o modelo tem que ter uma chave única (uniq key). No arquivo do modelo “fake” a chave da tabela combinada da procedure tem que ser precedida pelo comando [Key] (e para isso é preciso acrescentar a diretiva, no topo do arquivo de modelo, using System.ComponentModel.DataAnnotations). Aí, mesmo sem ser um arquivo físico ,a procedure ser comportará como se assim o fosse;
  • A consulta SQL sempre deve retornar dados para todas as propriedades da entidade. Então, basicamente, sua consulta SQL sempre vai ser algo como “Select * from {NomeDaTabela}” (sempre de uma única tabela dentre as que já estão mapeadas ou o da tabela “fake”).
    É até possível filtrar a tabela, usando parâmetros que funcionarão como uma cláusula “where”. Porém a procedure sempre irá retornar todos os campos da tabela (simples ou “fake”), retornando listas (claro que a procedure poderá trabalhar com outras tabelas, fazendo outras operações, mas o método FromSql sempre retornará uma lista do tipo “select * from Tabela” ou,em alguns casos, um valor único como “Select Count(*) from Tabela“. Você é quem decide: se o resultado é uma lista, injeta num .ToList(). Se o resultado é único, injeta num .SingleOrDefault();
  • Os nomes das colunas no conjunto de resultados sempre devem corresponder exatamente aos nomes das colunas às quais as propriedades são mapeadas (no modelo, ou Model, do contrário vai dar erro e s colunas que não batem virão com valores nulos).

Não há sentido em se usar o FromSql se não houver necessidade de filtrar a tabela com parâmetros ou necessidade de fazer operações acessórias dentro da procedure (atualizando outras tabelas). A procedure abaixo é praticamente inútil, no sentido de que poderia ser facilmente obtida sem nenhuma procedure:

CREATE PROCEDURE lista_todas_consultorias
AS
BEGIN
	SET NOCOUNT ON;
	SELECT * from consultorias
END
GO

Ela teria que usar, no controller, algo do tipo:

List<Consultorias> ListaDeConsultorias = _context.Consultorias.FromSql("lista_todas_consultorias").ToList;

Mas isso poderia ser obtido de várias outras formas, pois a tabela Consultorias já é mapeada, sendo possível obter a lista como no como por exemplo:

var ListagemDeConsultorias = await _context.Consultorias.ToListAsync();

Procedures simples com parâmetros usando FromSql

Já quando você que utilizar uma tabela já mapeada usando filtros (passando parâmetros), começa a fazer sentido usar o FromSql. Por exemplo, selecionar pessoas que estão ligadas a uma determinada Consultoria (considerando que as pessoas estão numa tabela já mapeada):

CREATE PROCEDURE seleciona_pessoas_duma_consultoria 
	@idConsultoria int
AS
BEGIN
	SET NOCOUNT ON;
	SELECT *  FROM dbo.ac_redepessoas
WHERE        (idConsultoria = @idConsultoria)
END
GO

Mesmo este uso, considerando que a tabela de pessoas já é mapeada e que contém o campo IdConsultoria, é bobinho. A instrução FromSql seria:

int idConsultoria = 1;
List<AcRedepessoas> ListagemDePessoasDaConsultoria = _context.AcRedepessoas.FromSql("seleciona_pessoas_duma_consultoria @p0", idConsultoria).ToList;

Mas isso também seria facilmente obtido diretamente no Core:

ListagemDePessoasDaConsultoria =  _context.AcRedepessoas.Where(x => x.IdConsultoria == idConsultoria).ToList();

Então, o uso de FromSql deve ocorrer em situações onde o Core MVC não pode, automaticamente, atualizar alguma tabela secundária ou fazer operações intermediárias. Também não se presta para operações do tipo Insert, Update e Delete.

Passando mais de um parâmetro

Algumas vezes você precisa passar mais de um parâmetro. O FromSql() tem um “overload”que aceita os parâmetros do objeto []. A primeira coisa a notar é o nome do parâmetro é “@p0”. Se houver mais parâmetros, incremente o contador como @p1, @p2 etc… O código seguinte mostra como passar vários parâmetros:

var ListagemDeConsultorias = await _context.Consultorias.ToListAsync();
int idConsultoria = 1;
var NomesDePessoas = "Roberto";
var ListagemDePessoasDaConsultoria = _context.AcRedepessoas
    .FromSql("ListaPessoasPorConsultoriaENome @p0, @p1",
    parameters: new[] { idConsultoria.ToString(), NomesDePessoas })
    .ToList();

Um cuidado: ao passar números inteiros, converta para string antes (se não vai dar erro no SQL). Existe também outra maneira de passar os parâmetros. Substitua @p0 por {0} (e se tiver mais parâmetros, @p1 por {1} e assim por diante.

int idConsultoria = 1;
var parametroConsultoria = new SqlParameter("@idDaConsultoria", idConsultoria);
List<acRedepessoas> ListaDePessoas = _context.acRedePessoas
            .FromSql("proc_lista_pessoas @idDaConsultoria", parametroConsultoria)
            .ToList();

Nesse formato, não é preciso antes converter o inteiro em “string“. Para passar 4 parâmetros, por exemplo:

var userType = _context.acRedepessoas.FromSql("dbo.AlgumaProcedure @Id = {0}, @Nome = {1}", 45, "Ada");

 

 

 

 

Para casos mais complexos, é preciso usar ExecuteSqlComand

Quando se trata de alterar um arquivo com procedure, este é comando que executa a procedure: ExecuteSqlComand, adequado para operações do tipo Insert, Update e Delete.

O que quase lugar nenhum explica é o seguinte: o comando ExecuteSqlComand não retorna listas. Retorna um inteiro. 0, se a operação foi bem sucedida. É só isso. Novamente: não há sentido em se usar esse comando a menos que você esteja atualizando outras tabelas intermediárias ou fazendo cálculos complexos antes de alterar ou inserir em registro.

A procedure a seguir insere um registro na tabela de Consultorias Categorias.

CREATE PROCEDURE usp_InsereConsultoria
 @NomeDaConsultoria nvarchar(300)
AS
BEGIN
    SET NOCOUNT ON;
    Insert into Consultorias Values (@NomeDaConsultoria)
END
GO

ExecuteSqlCommand deve ser usado em context.Database (na minha aplicação de exemplo _context = contexto;). Você pode usá-lo da seguinte maneira.

var NomeConsultoria= "José Consultores e Associados";
_context.Database
           .ExecuteSqlCommand("usp_InsereConsultoria @p0", NomeConsultoria);

É possível rodar o ExecuteSqlCommand no modo assíncrono também, como por exemplo:

await _context.Database.ExecuteSqlCommandAsync("usp_CriaConsultoria @p0, @p1", 
        parameters: new[] { "Nome Completo da Consultoria", "Nome de Guerra" });

 

A procedures mostradas anteriormente são muito simples. O problema é quando ser quer manusear resultados de diversas tabelas, amarrando umas às outras (joining).

O exemplo seguinte é baseado em uma aplicação que é essencialmente um questionário de avaliação de uma pessoa (qe se avalia e é avaliada por terceiros).

Ocorre que um questionário é sempre cadastrado por uma consultoria (e uma consultoria pode ter diversos questionários cadastrados). Quando ela quer aplicar o questionário para uma pessoa que vai ser avaliada, o sistema gera formulários de resposta para o avaliado e para todos avaliadores do avaliado. Quando qualquer uma das pessoas entra no sistema, ela acessa o questionário específico que deve responder. Nesta aplicação as respostas tem uma régua com todas alternativas (amarrada também ao modelo do questionário).

Ao acessar o formulário de respostas, o respondente tem de ler cada pergunta de cada questionário e salvar sua resposta. Há portanto uma tabela de respostas que guarda a NOTA dada (poderia ser uma resposta em texto). Essa tabela, no entanto, está amarrada:

  • a uma tabela de Avaliações, que tem a lista de todas avaliações do sistema, identificando o avaliador e o avaliado;
  • que por sua vez está amarrada a uma tabela de pessoas, que tem todos os participantes do sistema, de onde é pego o nome do Avaliado;
  • que por sua vez está amarrada a uma tabela de Consultorias, pois cada participante pertence a apenas uma Consultoria (e aqui pego o nome da Consultoria.

A tabela de Respostas também está amarrada:

  • A uma tabela que contém o texto das perguntas;
  • que por sua vez está amarrada a uma tabela que contém as competências a que cada pergunta pertence (com seu título e descrição);
  • que está amarrada a uma tabela de questionários modelos,
  • que está amarrada a uma tabela de tipos de questionários,
  • que finalmente está amarrada a um tipo de régua específico para cada questionário.

O modelo visual em SQL é o seguinte:

Construindo um modelo (model)

A primeira coisa a fazer é constrir um modelo que contenha todos os dados que serão usados, extraídos de todas fabelas acima. No caso. o fromulárioViewModel.cs:

namespace Coaching.Models.FormularioViewModels
{
    public class FormularioViewModel
    {
        public int IdRespostaAvaliacao { get; set; }
        public int? IdAvaliacao { get; set; }
        public int? IdPerguntaBanco { get; set; }
        public double? Nota { get; set; }
        public int IdAvaliado { get; internal set; }
        public int IdAvaliador { get; internal set; }
        public string TipoDeAvaliador { get; internal set; }
        public string NomeDoAvaliado { get; internal set; }
        public string NomeConsultoriaMenu { get; internal set; }
        public int? IdTipoQuestionario;
        public string TituloCompetencia;
        public string DescCompetencia;
        public string TituloCompetenciaLimpo;
        public string DescCompetenciaLimpo;
        public string OrientadoPara;
        public string NomeQuestionario;
        public int? IdConsultoria;
        public string NomeRegua;
        public int IdRegua;
        public int IdCompetenciaBanco;
        public int? OrdemPergunta;
        public string AssertivaAuto;
        public string AssertivaGeral;

       
    }
}

Esse modelo tem tudo que é necessário para selecionar e para mostrar uma única pergunta e sua resposta.

Preparando o Controller

Usando Linq, somente para pegar a lista de possíveis respostas de uma única avaliação, olha o tamanho do Join necessário:

var listarespostas = await (from a in _context.QuestionarioTipos
                                        join b in _context.QuestionariosModelos on a.IdTipoQuestionario equals b.IdTipoQuestionario
                                        join c in _context.PerguntasBancoGeral on b.IdPerguntaBanco equals c.IdPerguntaBanco
                                        join d in _context.CompetenciasBancoGeral on c.IdCompetenciaBanco equals d.IdCompetenciaBanco
                                        join e in _context.Reguas on a.IdRegua equals e.IdRegua
                                        join f in _context.AvaliacoesRespostas on c.IdPerguntaBanco equals f.IdPerguntaBanco
                                        join g in _context.Avaliacoes on f.IdAvaliacao equals g.IdAvaliacao
                                        join h in _context.AcRedepessoas on g.IdAvaliado equals h.IdUsuario
                                        join i in _context.Consultorias on h.IdConsultoria equals i.IdConsultoria
                                        where g.IdAvaliacao == id
                                        select new Models.FormularioViewModels.FormularioViewModel
                                        {
                                            NomeConsultoriaMenu = i.NomeConsultoriaMenu,
                                            NomeDoAvaliado = h.Nome,
                                            TipoDeAvaliador = g.TipoDeAvaliador,
                                            IdAvaliador = g.IdAvaliador,
                                            IdAvaliado = g.IdAvaliado,
                                            AssertivaAuto = c.Assertivaauto,
                                            AssertivaGeral = c.Assertivageral,
                                            OrdemPergunta = b.OrdemPergunta,
                                            IdCompetenciaBanco = d.IdCompetenciaBanco,
                                            IdRegua = e.IdRegua,
                                            NomeRegua = e.NomeRegua,
                                            IdConsultoria = a.IdConsultoria,
                                            NomeQuestionario = a.NomeQuestionário,
                                            OrientadoPara = a.OrientadoPara,
                                            DescCompetenciaLimpo = d.DescCompetenciaLimpo,
                                            TituloCompetenciaLimpo = d.TituloCompetenciaLimpo,
                                            DescCompetencia = d.DescCompetencia,
                                            TituloCompetencia = d.TituloCompetencia,
                                            IdTipoQuestionario = b.IdTipoQuestionario,
                                            IdAvaliacao = f.IdAvaliacao,
                                            IdPerguntaBanco = f.IdPerguntaBanco,
                                            IdRespostaAvaliacao = f.IdRespostaAvaliacao,
                                            Nota = f.Nota
                                        })
                                        .OrderBy(b => b.OrdemPergunta)
                                        .OrderBy(c => c.IdPerguntaBanco)
                                        .ToListAsync();

Para usar uma procedure equivalente ao modelo, é preciso gerá-la no servidor (pode ser via o próprio Visual Studio, mas é melhor usar o seu Microsoft Managment SQL Studio. O VS, mesmo 2017, é lento demais):

CREATE PROCEDURE visualiza_lista_respostas 
	-- Add the parameters for the stored procedure here
	@idQuestionario int 

AS
BEGIN
	-- SET NOCOUNT ON added to prevent extra result sets from
	-- interfering with SELECT statements.
	SET NOCOUNT ON;

    -- Insert statements for procedure here
	SELECT dbo.questionario_tipos.idTipoQuestionario AS IdTipoQuestionario, dbo.competencias_banco_geral.tituloCompetencia AS TituloCompetencia, dbo.competencias_banco_geral.descCompetencia AS DescCompetencia, 
             dbo.competencias_banco_geral.tituloCompetenciaLimpo AS TituloCompetenciaLimpo, dbo.competencias_banco_geral.descCompetenciaLimpo AS DescCompetenciaLimpo, dbo.questionario_tipos.orientadoPara AS OrientadoPara, 
             dbo.questionario_tipos.NomeQuestionário AS NomeQuestionario, dbo.questionario_tipos.idConsultoria AS IdConsultoria, dbo.reguas.NomeRegua, dbo.questionario_tipos.idRegua AS IdRegua, dbo.competencias_banco_geral.idCompetenciaBanco AS IdCompetenciaBanco, 
             dbo.questionarios_modelos.OrdemPergunta, dbo.avaliacoes_respostas.idRespostaAvaliacao AS IdRespostaAvaliacao, dbo.avaliacoes_respostas.idAvaliacao AS IdAvaliacao, dbo.avaliacoes_respostas.idPerguntaBanco AS IdPerguntaBanco, 
             dbo.avaliacoes_respostas.nota AS Nota, dbo.perguntas_banco_geral.assertivageral AS AssertivaGeral, dbo.avaliacoes.idAvaliado AS IdAvaliado, dbo.avaliacoes.idAvaliador AS IdAvaliador, dbo.avaliacoes.tipoDeAvaliador AS TipoDeAvaliador, 
             dbo.avaliacoes.idTipoquestionario AS idTipoQuestionario, dbo.ac_redepessoas.Nome AS NomeDoAvaliado, dbo.consultorias.nomeConsultoria AS NomeConsultoriaMenu, dbo.perguntas_banco_geral.assertivaauto AS AssertivaAuto
FROM   dbo.questionario_tipos INNER JOIN
             dbo.questionarios_modelos ON dbo.questionario_tipos.idTipoQuestionario = dbo.questionarios_modelos.idTipoQuestionario INNER JOIN
             dbo.perguntas_banco_geral ON dbo.questionarios_modelos.idPerguntaBanco = dbo.perguntas_banco_geral.idPerguntaBanco INNER JOIN
             dbo.competencias_banco_geral ON dbo.perguntas_banco_geral.idCompetenciaBanco = dbo.competencias_banco_geral.idCompetenciaBanco INNER JOIN
             dbo.reguas ON dbo.questionario_tipos.idRegua = dbo.reguas.idRegua INNER JOIN
             dbo.avaliacoes_respostas ON dbo.perguntas_banco_geral.idPerguntaBanco = dbo.avaliacoes_respostas.idPerguntaBanco INNER JOIN
             dbo.avaliacoes ON dbo.avaliacoes_respostas.idAvaliacao = dbo.avaliacoes.idAvaliacao INNER JOIN
             dbo.ac_redepessoas ON dbo.avaliacoes.idAvaliado = dbo.ac_redepessoas.idUsuario INNER JOIN
             dbo.consultorias ON dbo.ac_redepessoas.idConsultoria = dbo.consultorias.idConsultoria
WHERE (dbo.avaliacoes_respostas.idAvaliacao = @idQuestionario)
ORDER BY dbo.questionarios_modelos.OrdemPergunta, IdPerguntaBanco
END
GO

Para testar a procedure no servidor, usamos nosso conhecido comando execute (no exemplo abaixo, pegando todas respostas da avaliação número 40):

execute visualiza_lista_respostas 40
-- ou, se só quiser pegar um valor de retorno (0 no caso de sucesso)
DECLARE @return_value intDECLARE @return_value int
EXEC @return_value = [dbo].[visualiza_lista_respostas] @idQuestionario = 40
SELECT 'Return Value' = @return_value
GO

Rodando o execute no Servidor SQL, temos (visão parcial de um formulário selecionado):

retorno procedure lista formulario

Para rodar num controller do MVC, será preciso usar o comando ExecuteSqlCommand . 

No entanto, precisamos ter ao meno uma view (visualizador). Usando o scafolding, vamos gerar uma view simples sobre o modelo já implementado, Daremos a esta primeira ação da view o nome respondente_formulario

 

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Usando SQL Storage Procedures no CORE MVC – parte 2

Usando SQL Storage Procedures no CORE MVC – Parte 1

Usando SQL Storage Procedures no CORE MVC – Parte 1

Um dos problemas complicados no Core MVC é que não dá para usar as Views do SQL. Quando você tem de mostrar dados que tem diversas tabelas agrupadas, há algumas formas de contornar isso, usando procedures do SQL.

Se você vai operar com procedures, antes de tudo instale pelo Gerenciador do NuGet – se ainda não o tem instalado na sua aplicação, o Microsoft.AspNetCore.Identity.EntityFrameworkCore.

No arquivo do controlador que tiver actions com SQL, em alguns casos poderá será preciso acrescentar a diretiva using System.Data.SqlClient;.

Casos do método FromSql

Para executar procedures, use o método FromSql executa consultas RAW SQL. Mas existem algumas limitações para executar consultas RAW SQL ou procedures.

  • As consultas SQL só podem ser usadas para retornar tipos de entidades que fazem já parte do seu modelo. Portanto, não podem conter dados relacionados ou “montados” através de procedures. Existe uma forma de contornar isso, mas ninguém explica direito. Você deve montar um modelo “fake”, falso mesmo. Que não existe fisicamente no banco de dados. Depois de montar o modelo tem que fazer duas coisas:
    • incluir esse modelo “fake” no seu arquivo SuaAplicacaoContext.cs, dentro do diretório de modelos (Models);
    • o modelo tem que ter uma chave única (uniq key). No arquivo do modelo “fake” a chave da tabela combinada da procedure tem que ser precedida pelo comando [Key] (e para isso é preciso acrescentar a diretiva, no topo do arquivo de modelo, using System.ComponentModel.DataAnnotations). Aí, mesmo sem ser um arquivo físico ,a procedure ser comportará como se assim o fosse;
  • A consulta SQL sempre deve retornar dados para todas as propriedades da entidade. Então, basicamente, sua consulta SQL sempre vai ser algo como “Select * from {NomeDaTabela}” (sempre de uma única tabela dentre as que já estão mapeadas ou o da tabela “fake”).
    É até possível filtrar a tabela, usando parâmetros que funcionarão como uma cláusula “where”. Porém a procedure sempre irá retornar todos os campos da tabela (simples ou “fake”), retornando listas (claro que a procedure poderá trabalhar com outras tabelas, fazendo outras operações, mas o método FromSql sempre retornará uma lista do tipo “select * from Tabela” ou,em alguns casos, um valor único como “Select Count(*) from Tabela“. Você é quem decide: se o resultado é uma lista, injeta num .ToList(). Se o resultado é único, injeta num .SingleOrDefault();
  • Os nomes das colunas no conjunto de resultados sempre devem corresponder exatamente aos nomes das colunas às quais as propriedades são mapeadas (no modelo, ou Model, do contrário vai dar erro e s colunas que não batem virão com valores nulos).

Não há sentido em se usar o FromSql se não houver necessidade de filtrar a tabela com parâmetros ou necessidade de fazer operações acessórias dentro da procedure (atualizando outras tabelas). A procedure abaixo é praticamente inútil, no sentido de que poderia ser facilmente obtida sem nenhuma procedure:

CREATE PROCEDURE lista_todas_consultorias
AS
BEGIN
	SET NOCOUNT ON;
	SELECT * from consultorias
END
GO

Ela teria que usar, no controller, algo do tipo:

List<Consultorias> ListaDeConsultorias = _context.Consultorias.FromSql("lista_todas_consultorias").ToList();

Mas isso poderia ser obtido de várias outras formas, pois a tabela Consultorias já é mapeada, sendo possível obter a lista como no como por exemplo:

var ListagemDeConsultorias = await _context.Consultorias.ToListAsync();

Procedures simples com parâmetros usando FromSql

Já quando você que utilizar uma tabela já mapeada usando filtros (passando parâmetros), começa a fazer sentido usar o FromSql. Por exemplo, selecionar pessoas que estão ligadas a uma determinada Consultoria (considerando que as pessoas estão numa tabela já mapeada):

CREATE PROCEDURE seleciona_pessoas_duma_consultoria 
	@idConsultoria int
AS
BEGIN
	SET NOCOUNT ON;
	SELECT *  FROM dbo.ac_redepessoas
WHERE        (idConsultoria = @idConsultoria)
END
GO

Mesmo este uso, considerando que a tabela de pessoas já é mapeada e que contém o campo IdConsultoria, é bobinho. A instrução FromSql seria:

int idConsultoria = 1;
List<AcRedepessoas> ListagemDePessoasDaConsultoria = _context.AcRedepessoas.FromSql("seleciona_pessoas_duma_consultoria @p0", idConsultoria).ToList;

Mas isso também seria facilmente obtido diretamente no Core:

ListagemDePessoasDaConsultoria =  _context.AcRedepessoas.Where(x => x.IdConsultoria == idConsultoria).ToList();

Então, o uso de FromSql deve ocorrer em situações onde o Core MVC não pode, automaticamente, atualizar alguma tabela secundária ou fazer operações intermediárias. Também não se presta para operações do tipo Insert, Update e Delete.

Passando mais de um parâmetro

Algumas vezes você precisa passar mais de um parâmetro. O FromSql() tem um “overload”que aceita os parâmetros do objeto []. A primeira coisa a notar é o nome do parâmetro é “@p0”. Se houver mais parâmetros, incremente o contador como @p1, @p2 etc… O código seguinte mostra como passar vários parâmetros:

var ListagemDeConsultorias = await _context.Consultorias.ToListAsync();
int idConsultoria = 1;
var NomesDePessoas = "Roberto";
var ListagemDePessoasDaConsultoria = _context.AcRedepessoas
    .FromSql("ListaPessoasPorConsultoriaENome @p0, @p1",
    parameters: new[] { idConsultoria.ToString(), NomesDePessoas })
    .ToList();

Um cuidado: ao passar números inteiros, converta para string antes (se não vai dar erro no SQL). Existe também outra maneira de passar os parâmetros. Substitua @p0 por {0} (e se tiver mais parâmetros, @p1 por {1} e assim por diante.

int idConsultoria = 1;
var parametroConsultoria = new SqlParameter("@idDaConsultoria", idConsultoria);
List<acRedepessoas> ListaDePessoas = _context.acRedePessoas
            .FromSql("proc_lista_pessoas @idDaConsultoria", parametroConsultoria)
            .ToList();

Nesse formato, não é preciso antes converter o inteiro em “string“. Para passar 4 parâmetros, por exemplo:

var userType = _context.acRedepessoas.FromSql("dbo.AlgumaProcedure @Id = {0}, @Nome = {1}", 45, "Ada");

Para casos mais complexos, é preciso usar ExecuteSqlComand

Quando se trata de alterar um arquivo com procedure, este é comando que executa a procedure: ExecuteSqlComand, adequado para operações do tipo Insert, Update e Delete.

O que quase lugar nenhum explica é o seguinte: o comando ExecuteSqlComand não retorna listas. Retorna um inteiro. 0, se a operação foi bem sucedida. É só isso. Novamente: não há sentido em se usar esse comando a menos que você esteja atualizando outras tabelas intermediárias ou fazendo cálculos complexos antes de alterar ou inserir em registro.

A procedure a seguir insere um registro na tabela de Consultorias Categorias.

CREATE PROCEDURE usp_InsereConsultoria
 @NomeDaConsultoria nvarchar(300)
AS
BEGIN
    SET NOCOUNT ON;
    Insert into Consultorias Values (@NomeDaConsultoria)
END
GO

ExecuteSqlCommand deve ser usado em context.Database (na minha aplicação de exemplo _context = contexto;). Você pode usá-lo da seguinte maneira.

var NomeConsultoria= "José Consultores e Associados";
_context.Database
           .ExecuteSqlCommand("usp_InsereConsultoria @p0", NomeConsultoria);

É possível rodar o ExecuteSqlCommand no modo assíncrono também, como por exemplo:

await _context.Database.ExecuteSqlCommandAsync("usp_CriaConsultoria @p0, @p1", 
        parameters: new[] { "Nome Completo da Consultoria", "Nome de Guerra" });

Outro exemplo, usando diretamente dados de um modelo (model) diretamente na chamada da procedure. Após salvar os dados em edição, a procedure executa diversas tarefas de atualização em outras tabelas – e por isso é chamada logo após o “save”:

_context.Update(avaliacoesRespostas);
            await _context.SaveChangesAsync();
            await _context.Database.ExecuteSqlCommandAsync("atualiza_nota @p0, @p1",
        parameters: new[] { avaliacoesRespostas.Nota, avaliacoesRespostas.IdRespostaAvaliacao });
            return RedirectToAction("RespondeFormulario", "Avaliacoes", new { id = avaliacoesRespostas.IdAvaliacao });

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Paginação nas Grades do Bootstrap do CORE MVC (que funciona)

Paginação nas Grades do Bootstrap do CORE MVC (que funciona)

Paginação nas Grades do Bootstrap do CORE MVC (que funciona)

Há diversos componentes para fazer paginação de grades no Core MC. Alguns não funcionam, outros estão desatualizados. E a sugestão dada pela própria Microsoft (referência 1), é muito pobre e limitada. Você pode usar o componente cloudscribe.Web.Pagination, que é atualizado e bem poderoso.

Você pode baixar / clonar este repo e executar o projeto PagingDemo.Web para ver várias páginas de demonstração usando o paginador em várias configurações, incluindo ajax e ajax dentro de um modal bootstrap.

Se você vai operar com procedures, antes de tudo instale pelo Gerenciador do NuGet – se ainda não o tem instalado na sua aplicação, o Microsoft.AspNetCore.Identity.EntityFrameworkCore.

Instalando o cloudscribe

Prérequistos:

Para instalar a partir do nuget.org, abra o arquivo project.json do seu aplicativo da Web e na seção de dependências adicione:

"cloudscribe.Web.Pagination": "1.1.*"

Com o Visual Studio 2017 não precisa fazer nada além de ir no gerenciador de pacotes, pesquisar o cloudscribe e pedir para instalar no seu projeto/aplicação. Pode também instalar, se quiser, direto pelo Packet Manager Console. As duas opções estão no menu de Ferramentas (Tools).

Começando pelo Modelo

No diretório de Modelos (Models), crie um modelo que contenha os dados que você quer mostrar na grade (se for um modelo simples, de uma única tabela que você já tenha, use o modelo dessa tabela). O modelo que usei tem dados de duas tabelas (um de pessoas cadastradas no sistema e outra de consultorias (empresas) cadastradas. Meu objetico é mostrar todos os consultores e suas respectivas consultorias (sendo que uma consultoria pode ter um ou mais consultores no sistema).

Modelo de dados

Criei na pasta Models o arquivo Consultores.cs, com os seguites campos:

using cloudscribe.Web.Pagination;
using System.ComponentModel.DataAnnotations;


namespace Coaching.Models //somente para visualização
{
    public class Consultores
    {
        // arquivo de pessas
        [Key]
        public int IdUsuario { get; set; }
        [Display(Name = "Nível de Acesso")]
        public int? NiveldeAcesso { get; set; }
        [Display(Name = "Nome do Consultor")]
        public string Nome { get; set; }
        //arquivo de consultorias
        public int IdConsultoria { get; set; }
        [Required(ErrorMessage = "Favor entrar com o nome da sua consultoria (ou seu nome).")]
        [Display(Name = "Nome da Consultoria")]
       
        public string NomeConsultoria { get; set; }
        public string Telefones { get; set; }
        public string Cidade { get; set; }
        [Display(Name = "Link de Acesso")]
        public string LinkAcesso { get; set; }
        
    }
}

Modelo (OBRIGATÓRIO) de Auxílio para a Paginacão

Crie um modelo muito simples para auxiliar na paginação. No caso, criei o arquivo Paginacao.cs na pasta de modelos (Models). Qualquer que seja o seu modelo de dados (como o acima), este arquivo será “comum” a todas as grids do seu sistema – e nelas quiser ter a paginação.

using cloudscribe.Web.Pagination;
using System.Collections.Generic;


namespace Coaching.Models
{
    public class Paginacao

    {
        public Paginacao()
        {
            Paging = new PaginationSettings();
        }

        public string Query { get; set; } = string.Empty; //só use se for implementar um grid com pesquisa, se não não precisa estar no modelo

        //public List<Product> Products { get; set; } = null; //não precisa neste exemplo
        //public List<Consultores> ListaConsultores { get; set; } = null; //não precisa neste exemplo

        public PaginationSettings Paging { get; set; }
    }
}

Implantando o serviço de Paginação no Startup

No seu Startup.cs você precisará disso em ConfigureServices:

services.AddCloudscribePagination();

É isso.

Implantando o Tag Helper de Paginação de uma forma genérica

Como você pode querer usar a paginação em qualquer página que tenha uma grade (grid), a opção de incluir num arquivo auxiliar de visualização que é sempre lido (o _ViewImports.cshtml) é a opção mais inteligente. Você, assim, não precisa “solicitar” o tag help nas páginas de visualização. Ele estará sempre presente.

Assim, no arquivo  _ViewImports.cshtml, inclua:

@addTagHelper "*, cloudscribe.Web.Pagination"

Colocando o Tag Helper de Paginação na Visualização (View)

Você vai precisar de alguma coisa parecida com isso:


&amp;amp;lt;div&amp;amp;gt;
                    &amp;amp;lt;span class=&quot;Navigator&quot;&amp;amp;gt;
                        @*navegador*@
                        @*paginação*@
                        &amp;amp;lt;cs-pager cs-paging-pagesize=&quot;@ViewBag.PageSize&quot; cs-paging-pagenumber=&quot;@ViewBag.PageNumber&quot; cs-paging-totalitems=&quot;@ViewBag.TotalItems&quot; cs-show-first-last=&quot;true&quot; cs-pagenumber-param=&quot;page&quot; cs-suppress-empty-nextprev=&quot;true&quot; cs-suppress-inactive-firstlast=&quot;true&quot; cs-first-page-text=&quot;Primeiro&quot; cs-last-page-text=&quot;Último&quot; cs-pager-li-current-class=&quot;active&quot; cs-pager-li-non-active-class=&quot;disabled&quot; asp-controller=&quot;AcRedePessoas&quot; asp-action=&quot;ConsultoresLista&quot;&amp;amp;gt;&amp;amp;lt;/cs-pager&amp;amp;gt;
                    &amp;amp;lt;/span&amp;amp;gt;
&amp;amp;lt;/div&amp;amp;gt;

Os parametros de tamanho da página (ViewSize), número da página (PageNumber) e número de itens a serem mostrados (TotalItens) são passados através do controlador (controller), que lê os dados da tabela (que veremos mais adiante). No caso deste exemplo, o controlador (controller) é o AcRedePessoas (todos usuários do sistema) que tem uma ação (Action) chamada ConsultoresLista.

Voce pode colocar esta div no final de sua grade. Ou repetí-la, colocando antes do início da grade e depois do término da grade. No caso deste exemplo, optei por deixá-la só no final da grade. O arquiivo de listagem (vizualização) que chamei de ConsultoresLista.cshtml foi gerado com um “scafolding” no modelo de visualização definido no início.

Atenção: se seu molelo não fizer parte do contexto (não for uma tabela simples do sistema), você precisará incluir o mesmo no seu ArquivodeContexto.cs, dentro da pasta de modelos (Models). Só depois disso você recompila o projeto e faz o scafolding.

No meu caso, a inclusão foi feita assim:

using Microsoft.EntityFrameworkCore;

namespace Coaching.Models
{
    public partial class Alicecapella119Context : DbContext
    {
        public virtual DbSet<AcRedepessoas> AcRedepessoas { get; set; }
        public virtual DbSet<Avaliacoes> Avaliacoes { get; set; }
        public virtual DbSet<AvaliacoesRespostas> AvaliacoesRespostas { get; set; }
        public virtual DbSet<CompetenciasBancoGeral> CompetenciasBancoGeral { get; set; }
        public virtual DbSet<Consultorias> Consultorias { get; set; }
        public virtual DbSet<EmailsEnviados> EmailsEnviados { get; set; }
        public virtual DbSet<Empresas> Empresas { get; set; }
        public virtual DbSet<Log> Log { get; set; }
        public virtual DbSet<ParametrosClicks> ParametrosClicks { get; set; }
        public virtual DbSet<PerguntasBancoGeral> PerguntasBancoGeral { get; set; }
        public virtual DbSet<QuestionarioTipos> QuestionarioTipos { get; set; }
        public virtual DbSet<QuestionariosModelos> QuestionariosModelos { get; set; }
        public virtual DbSet<Reguas> Reguas { get; set; }
        public virtual DbSet<ReguasEscalas> ReguasEscalas { get; set; }
        public virtual DbSet<Relacionamento> Relacionamento { get; set; }
        
        //para procedure ou vizualição via linq
        public virtual DbSet<FormularioViewModels.FormularioViewModel> Formulario { get; set; } //visualiza_lista_respostas
        public virtual DbSet<Consultores> Consultores { get; set; } //visualiza_consultores

Note que a última linha acrescenta o modelo de Consultores, como se fosse uma tabela falsa (fake). É útil apenas para visualização, não serve para edição ou deleção ou criação.

Último passo: o controlador da visualizaçao (view)

O controlador é recheado de “mancadas”. Na verdade, é o segredo da paginação.

Itens por página

Primeiro, você tem que definir quantos itens serão mostrados por página, cada vez que ela é mostrada. Isso é feito definindo-se uma variável privada:

private const int DefaultPageSize = 2;

Isso: no caso, quero que cada página apresente apenas 2 registros. Essa constante você define logo no início do controlador, antes das definições dos Actions.

public class AcRedepessoasController : Controller
    {
        private readonly Alicecapella119Context _context;
        private readonly IEmailService _emailService;
        //para navegação. Se tiver mais de uma ação e quiser diferentes tamanhos, defina mais constantes
        private const int DefaultPageSize = 8; //aqui vai sua constante
       
        public AcRedepessoasController(Alicecapella119Context context
            , IEmailService emailService
            )
        {
            _context = context;
            _emailService = emailService;
        }

Página Inicial

Segundo: quando você entra pela primeira vez na visualização (view), pode não ter uma indicação da página que quer ver. Assim é preciso definir que você quer a primeira página:

//Lista Consultores
        public IActionResult ConsultoresLista(int? page)
        {

            if (page == null)
            {
                page = 1;
            }

Total de Registros

Você não sabe quantos registros existem. No exemplo, quantos consultores existem. A alternativa é descobrir. No meu caso, os consultores são todos aqueles registrados com Nível de Acesso = 3:

var TotalDeConsultores = _context.AcRedepessoas.Where(x => x.NiveldeAcesso == 3).Count();

Carregando os dados numa Lista

Esta etapa é fundamental. Os dados precisam ser mostrados no vizualizador que foi montado pelo scafolding. No caso deste exemplo, a lista de consultores que quero mostrar vem de duas tabelas. Assim, criei uuma ListaDeConsultores, da seguinte forma:

var ListaDeConsultores = (from i in _context.AcRedepessoas
                                      join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                                      where i.NiveldeAcesso == 3
                                      select new Consultores
                                      {
                                          IdUsuario = i.IdUsuario,
                                          NiveldeAcesso = i.NiveldeAcesso,
                                          Nome = i.Nome,
                                          IdConsultoria = k.IdConsultoria,
                                          Cidade = k.Cidade,
                                          LinkAcesso = k.LinkAcesso,
                                          Telefones = k.Telefones
                                      })
                                    .OrderBy(p => p.Nome)
                                    .Skip(offset)
                                    .Take(DefaultPageSize)
                                    .ToList();

Observe que os campos definido obedecem cegamente aos campos do modelo definido em Consultores.cs, E que o modelo pega exatamente o número de registros necessários a partir de um  default (evitando sobrecarregar a memória do sistema), colocando-os numa lista.

Instanciando dados para a paginação

Para permitir o controle de paginação, você deve adicionar dados ao modelo de paginção, o que é gfeito da seguinte forma:

var model = new Paginacao();
            model.Paging.CurrentPage = currentPageNum;
            model.Paging.ItemsPerPage = DefaultPageSize;
            model.Paging.TotalItems = TotalDeConsultores;

Porém, como nosso modelo é simples, precisamos passar para a visualização (view), dados que permitam atualizar o tag helper. Isso é feito com View.Bags.

ViewBag.CurrentPage = currentPageNum;
            ViewBag.ItemsPerPage = DefaultPageSize;
            ViewBag.TotalItems = TotalDeConsultores;
            ViewBag.PageSize = DefaultPageSize;
            ViewBag.PageNumber = currentPageNum;

Uma visão geral  do controller seria, com todas as dicas:

//Lista Consultores
        public IActionResult ConsultoresLista(int? page)
        {
            if (page == null)
            {
                page = 1;
            }
            var currentPageNum = Convert.ToInt32(page);
            var offset = (DefaultPageSize * currentPageNum) - DefaultPageSize;
  
            var ListaDeConsultores = (from i in _context.AcRedepessoas
                                      join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                                      //join j in _context.AcRedepessoas on p.IdAvaliador equals j.IdUsuario
                                      //where p.DonorId.Equals(filter)
                                      where i.NiveldeAcesso == 3
                                      select new Consultores
                                      {
                                          IdUsuario = i.IdUsuario,
                                          NiveldeAcesso = i.NiveldeAcesso,
                                          Nome = i.Nome,
                                          IdConsultoria = k.IdConsultoria,
                                          Cidade = k.Cidade,
                                          NomeConsultoria=k.NomeConsultoria,
                                          LinkAcesso = k.LinkAcesso,
                                          Telefones = k.Telefones
                                      })
                                    .OrderBy(p => p.Nome)
                                    .Skip(offset)
                                    .Take(DefaultPageSize)
                                    .ToList();
            var TotalDeConsultores = (from i in _context.AcRedepessoas
                                      join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                                      where i.NiveldeAcesso == 3
                                      select new Consultores
                                      {
                                          IdUsuario = i.IdUsuario,
                                      })
                                    .Count();
            var model = new Paginacao();
  
            model.Paging.CurrentPage = currentPageNum;
            model.Paging.ItemsPerPage = DefaultPageSize;
            model.Paging.TotalItems = TotalDeConsultores;

            ViewBag.CurrentPage = currentPageNum;
            ViewBag.ItemsPerPage = DefaultPageSize;
            ViewBag.TotalItems = TotalDeConsultores;
            //ViewBag.PageSize = DefaultPageSize;
            ViewBag.PageSize = DefaultPageSize; //Convert.ToInt32(Math.Ceiling(Convert.ToDouble(TotalDeConsultores) / Convert.ToDouble(DefaultPageSize)));//currentPageNum;
            //ViewBag.PageNumber = Convert.ToInt32( Math.Ceiling(Convert.ToDouble(TotalDeConsultores) / Convert.ToDouble(DefaultPageSize)));//currentPageNum;
            ViewBag.PageNumber = currentPageNum;
            return View(ListaDeConsultores);
      }

Note que também são passados os parâmetros para o modelo de paginação, ante (ou depois, não importa) dos ViewBags.

 

O Resultado

O resultado é muito bom. Veja imagens:

Ou ainda, depois de clicar em Último:

Conclusão

O tag helper suporta MUITAS opções. Vejamos quais são:

  • cs-paging-pagenumber – 1 (tem que ser um inteiro)
  • cs-paging-totalitems – 1 (é o total de  itens no banco de dados)
  • cs-paging-maxpageitems – 10 (o máximo a ser mostrado por pégina)
  • cs-pagenumber-param – pageNumber (o nome do parametro vai estar no seu controlador – (“controller”)
  • cs-show-first-last – false (se mostra primeiro ou final no controle)
  • cs-first-page-text – < (string do mostra primeiro)
  • cs-first-page-title – First Page (Titulo do Mostra primeiro)
  • cs-last-page-text – > (texto da última página)
  • cs-last-page-title – Last Page (título da última página)
  • cs-previous-page-text – « (símbolo da página anterior)
  • cs-previous-page-title – Previous page (título da Página anterior)
  • cs-next-page-text – » (símbolo da próxima página)
  • cs-next-page-title – Next page (título da próxima página)
  • cs-pager-ul-class – pagination
  • cs-pager-li-current-class – active
  • cs-pager-li-non-active-class – disabled
  • cs-ajax-target – empty – should be the id of the html element to target for ajax updates
  • cs-ajax-mode – replace
  • cs-ajax-success – empty – a callback
  • cs-ajax-failure – empty – a callback
  • cs-ajax-loading – empty – the #id of an image ie animated gif to show while loading
  • cs-ajax-loading-duration – empty – corresponds to data-ajax-loading-duration

O componente é “inteligente” e só aparece na página se o número de registros for MAIOR que a quantidade máxima por página… Por isso, não se assuste nos testes se ele não aparecer… Para testar, use um valor pequeno como 2 o 3 itens por página, principalmente se seu arquivo ainda não está populado.

Para usar o ajax, você deve incluir jquery.unobtrusive-ajax.js na página. Para usar o AlphaPagerTagHelper, você adicionaria algo assim no seu arquivo visualizador (view):

<cs-alphapager cs-alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    cs-selected-letter="@Model.Query"
    cs-all-label="All"
    asp-controller="Paging"
    asp-action="ProductList" 
    cs-selected-letter-param="query"
    ></cs-alphapager>

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Listas DropDown usando o Select Tag Helper no CORE MVC

Listas DropDown usando o Select Tag Helper no CORE MVC

Listas DropDown usando o Select Tag Helper no CORE MVC

Usando o tag helper SELECT, é muito fácil ter listas dropdown nas suas páginas de criação e edição. Só que como as funcionalidades mudaram muito desde o início do Core MVC, há muita informação na Internet que simplesmente não funciona mais. E você pode perder horas – ou mesmo dias – tentando entender o que está acontecendo até conseguir um simples select com uma lista dropdown. Este artigo mostra como fazer isso rapidamente.

Usando o Select Tag Helper

O tag help para um select é usado adicionando o atributo asp-for a um elemento selecionado. Por exemplo, considere uma classe de modelo de visão muito simples, simples contendo uma propriedade Cidade:

public class MeuModeloDeCidades
{
    public string Cidade{ get; set; }
}

Para gerar o seu select basta instanciar a propriedade Cidade no comando de select, dentro da sua página de visualização (View):

<select asp-for="Cidade"></select>

O tag helper irá gerar o seguinte código HTML:

<select name="Cidade" 
           id="Cidade">
</select>

Este é o princípio lógico. Mas fazendo isso, você não tem nenhuma lista de opções (as cidades) dentro do seu select – e ele não serve para nada. É preciso popular o select com todas opções desejadas.

Acrescentando opções no Select do Tag Helper

Se as opções são poucas – e não vem de um banco de dados, você poder fazer isso no próprio arquivo de visualização (View):

<select asp-for="Cidade">
    <option value="São Paulo">São Paulo</option>
    <option value="Rio de Janeiro">Rio de Janeiro</option>
    <option value="--">Outras Cidades</option>
</select>

Nesse caso, as opções especificadas na marcação serão incluídas no HTML gerado. A opção selecionada será determinada automaticamente com base no valor da propriedade do modelo. Por exemplo, se a propriedade Cidade estiver definida como ‘São Paulo’ no modelo, o seguinte HTML será gerado:

<select name="Cidade" id="Cidade">
    <option selected="selected" value="São Paulo">São Paulo</option>
    <option value="Rio de Janeiro">Rio de Janeiro</option>
    <option value="--">Outras Cidades</option>
</select>

Se a lista de opções for carregada dinamicamente a partir de uma base de dados, você pode usar o atributo auxiliar chamado tag asp-items. Tudo o que você precisa fazer é definir o atributo asp-items para um IEnumerable. Por exemplo, se tivéssemos uma lista de cidades disponíveis numa ViewBag, poderíamos especificar as opções de seleção da seguinte forma:

<select asp-for="Cidades" 
         asp-items="ViewBag.ListaDeCidades">
</select>

IMPORTANTE: Não se esqueça de fechar o elemento <SELECT> com </SELECT>.

Este é o princípio. Mas como popular a ViewBag??? A VIewBag é passado do controlador (controller) para o visualizador (view).

Carregando as opções do Select através do Banco de Dados

Há duas formas de fazer isso. Em seu arquivo de controle (controller), na ação (action) que vai estar associada à visualização, você popula o ViewBag. Vamos imaginar neste exemplo que a Viewbag será usada num visualizador para CRIAR um novo registro. Neste caso, o cliente poderá escolher qualquer opção (não há opção pré definida).

Jeito 1:

public IActionResult Cria()
        {
            List<SelectListItem> Lista = new List<SelectListItem>();
            Lista.Add(new SelectListItem //adiciona uma opção que convida a escolher uma das possíveis opções
            {
                Text = "Selecione uma Consultoria",
                Value = ""
            });
            foreach (var Linha in Dados)
            {
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdRelacionamento.ToString(),
                    Text = Linha.TipoDeRelacaoPublico,


                });
            }
            ViewBag.Selecionador = Lista;
         
            return View();
    }

Ou seja, em meu banco de dados tenho um arquivo de Consultorias, que faz parte do contexto (_context).  Quando registro um novo consultor no sistema, tenho de associá-lo a uma consultoria (já cadastrada). A tabela de pessoas vai guardar a Id da Consultoria no cadastramento da pessoa “consultora”. Por isso preciso do select, que chama a tabela de consultorias, mostra as mesmas pelo seu nome, mas ao gravar o arquivo o registro que é passado leva não o nome da consultoria – e sim a ID da consultoria.

Note que no comando foreach posso ordenar o select pelo nome das consultorias (.OrderBy(x=>x.NomeConsultoria). Também poderia incluir algum tipo de filtro acrescentado  .(Where x=>x.NomedoCampo == Uma string  ou um inteiro, conforme o campo).

Note também que ao passar valores inteiros (como uma id, é feita a conversãopara strign (z.IdConsultoria.ToString(), no caso).

A chamada, dentro do arquivo de visualização (View) fica da seguinte forma:

<div class="form-group">
            <label asp-for="IdConsultoria" class="col-md-2 control-label"></label>
            <div class="col-md-10">
             
                <select asp-for="IdConsultoria" class="form-control" asp-items="ViewBag.idConsultoria"></select>
                <span asp-validation-for="IdConsultoria" class="text-danger"></span>
              
            </div>
</div>

Jeito 2

Esta forma tem o “defeito” de não permitir a passagem, pelo controlador (controller) de uma opção tipo “Selecione uma das Consultorias”. Mas funciona.

public IActionResult Cria()
        {
           
         var Lista2 = from x in _context.Consultorias
                         orderby x.NomeConsultoria
                         select x;
            ViewBag.IdConsultoria2 = new SelectList(Lista2.AsNoTracking(), "IdConsultoria", "NomeConsultoria", "");

            return View();
    }

Mas, como no caso anterior, é possível ordenar e filtrar a seleção de opções usando clausulas where e orderby.

Para contornar esta limitação, você pode incluir no visualizador (vView) a seguinte linha, dentro da tag helper do select:

  <option value="">Selecione uma Consultoria</option>

Ficando assim:

<div class="form-group">
            <label asp-for="IdConsultoria" class="col-md-2 control-label"></label>
            <div class="col-md-10">
              <select asp-for="IdConsultoria" class="form-control" asp-items="ViewBag.idConsultoria2">
                    <option value="">Selecione uma Consultoria</option>
                </select>
                <span asp-validation-for="IdConsultoria" class="text-danger"></span>
   
            </div>
</div>

Habilitando Tag Helpers de forma genérica

Para que tag helpers funcionem em qualquer arquivo de visualização, a opção é habilitar no arquivo auxiliar de visualização que é sempre lido (o _ViewImports.cshtml) .

Assim, no arquivo  _ViewImports.cshtml, inclua:

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Isso pode ser feito com outros tag helpers que você vier a construir (além dos padrões Microsoft já existentes no Visual Studio).

Criando Funções para Popular os Tag Helper de Selects nos controladores de criação e edição

Ocorre que você pode precisar do mesmo “dropdown” não só em em páginas de criação, mas também páginas de edição.

No caso de edição, já havia uma opção pré escolhida..

.Não é justo fazer com que o usuário tenha que, novamente, definir uma opção que já tinha definido antes. Ele pode mudar de ideia, mas sempre a partir da ideia que já teve antes.

Então, o conceito aqui é gerar o conteúdo da ViewBag considerando que pode ter havido uma opção anterior. É criada uma função privada, que sempre é chamada pelos controladores (controllers) de criação ou edição. Exemplos:

Lendo dados do arquivo Users (quando o sistema tem login)

//FUNCOES POPULA LOOKUP PARA EDICAO
        //PopulateAvaliado(id1); -> chadado pelo controlador
        //PopulateAvaliador(id2);
        //PopulateTipoDeAvaliador(id3);
        private void PopulateAvaliado(object PessoaSelecionada = null)
        {
            var pessoaQuery = from d in _userManager.Users
                              orderby d.UserName
                              select d;

            ViewBag.PessoaAvaliadoId = new SelectList(pessoaQuery.AsNoTracking(), "Id", "Email", PessoaSelecionada);
            return;
         }

Uma forma que funciona muito bem e apresenta o Selecionador somente nos caos de criação (em que não existe opção pré selecionada:

private void PopulateTipoDeAvaliador(object TipoSelecionado = null)
        {
            List<SelectListItem> Lista = new List<SelectListItem>();
            if (TipoSelecionado == null)
            {
                Lista.Add(new SelectListItem()
                { Value = null, Text = "<<Selecione Tipo de Relacionamento>>", Selected = true });
            }

            var Dados = from r in _context.Relacionamento orderby r.TipoDeRelacaoPublico select r;
            foreach (var Linha in Dados)
            {
                bool valor = false;
                if (Linha.IdRelacionamento == Convert.ToInt32(TipoSelecionado))
                {
                    valor = true;
                }
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdRelacionamento.ToString(),
                    Text = Linha.TipoDeRelacaoPublico,
                    Selected = valor

                });
            }
            ViewBag.Selecionador = Lista;
        }

Lendo dados de um arquivo de Contexto (_context)

private void PopulateTipoDeAvaliador(object TipoSelecionado = null)
        {
            var relacaoQuery = from e in _context.TipoDeAvaliador
                               orderby e.TextoTipoDeAvaliador
                               select e;
            ViewBag.TipoDeRelacaoId = new SelectList(relacaoQuery.AsNoTracking(), "TipoDeAvaliadorID", "TextoTipoDeAvaliador", TipoSelecionado);
            return;
        }

Chamando os “lookups” dos selects do controlador de Criação:

// GET: Avaliacoes/Create
        public IActionResult Create()
        {
            //var id1 = avaliacoes.AvaliadoId;
            //var id2 = avaliacoes.AvaliadorId;
            // var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado();
            PopulateAvaliador();
            PopulateTipoDeAvaliador();
            return View();
        }

        // POST: Avaliacoes/Create
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("AvaliacaoId,AvaliadoId,AvaliadorId,DataCadastroAvaliacao,DataConviteAvaliacao,DataFimAvaliacao,DataUltimaAlteracao,DataUltimoLembrete,FlagFim,MediaDasRespostas,PerguntasRespondidas,TipoDeAvaliadorID,TotalDePerguntas")] Avaliacoes avaliacoes)
        {

            if (ModelState.IsValid)
            {
                _context.Add(avaliacoes);
                await _context.SaveChangesAsync();
                return RedirectToAction("Index");
            }
            var id1 = avaliacoes.AvaliadoId;
            var id2 = avaliacoes.AvaliadorId;
            var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado(id1);
            PopulateAvaliador(id2);
            PopulateTipoDeAvaliador(id3);
            return View(avaliacoes);
        }

Chamando os “lookups” dos selects do controlador de Edição:

// GET: Avaliacoes/Edit/5
        public async Task<IActionResult> Edit(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var avaliacoes = await _context.Avaliacoes.SingleOrDefaultAsync(m => m.AvaliacaoId == id);
            if (avaliacoes == null)
            {
                return NotFound();
            }
            var id1 = avaliacoes.AvaliadoId;
            var id2 = avaliacoes.AvaliadorId;
            var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado(id1);
            PopulateAvaliador(id2);
            PopulateTipoDeAvaliador(id3);
            return View(avaliacoes);
        }

        // POST: Avaliacoes/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(int id, [Bind("AvaliacaoId,AvaliadoId,AvaliadorId,DataCadastroAvaliacao,DataConviteAvaliacao,DataFimAvaliacao,DataUltimaAlteracao,DataUltimoLembrete,FlagFim,MediaDasRespostas,PerguntasRespondidas,TipoDeAvaliadorID,TotalDePerguntas")] Avaliacoes avaliacoes)
        {
            if (id != avaliacoes.AvaliacaoId)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    _context.Update(avaliacoes);
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (!AvaliacoesExists(avaliacoes.AvaliacaoId))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction("Index");
            }
            var id1 = avaliacoes.AvaliadoId;
            var id2 = avaliacoes.AvaliadorId;
            var id3 = avaliacoes.TipoDeAvaliadorID;

            PopulateAvaliado(id1);
            PopulateAvaliador(id2);
            PopulateTipoDeAvaliador(id3);
            return View(avaliacoes);
        }

É isso. Se tiver sugestões, contribua.

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.

Listas DropDown em Cascata (Cascading) usando o Select Tag Helper no CORE MVC

Listas DropDown em Cascata (Cascading) usando o Select Tag Helper no CORE MVC

Listas DropDown em Cascata (Cascading) usando o Select Tag Helper no CORE MVC

Neste artigo damos continuidade ao artigo de listas dropdown usando o tag helper SELECT. Fazer listas dropdown em cascata exige o uso de Jquery e chamadas Ajax.  Este artigo mostra como fazer isso, desde o setup do sistema até as rotinas do core MVC integradas ao javascript. Não é muito elementar. Mas funciona. Aqui vai o caminho das pedras.

Fazendo o setup do seu sistema CORE MVC no arquivo Startup.cs

Há muitos artigos na Internet que falam em Cascading (dropdown em cascata, um dependente do outro) – mas a maioria mostra o milagre mas não mostra o santo. Ao fazer uma chamada AJAX para o servidor, por questões de segurança, os navegadores modernos não permitem. Principalmente se a camada está em domínio diferente do domínio da sua aplicação. No meu caso, eu não conseguia acesso HTTP para a leitura de dados mesmo dentro do meu próprio ambiente de desenvolvimento. Não conseguia nem no LOCALHOST, resumindo.

Estudando os erros, vi que precisava autorizar a aplicação CORE MVC a ler o que eu queria, criando uma política de acesso para capturar dados de uma página fora da página que você está. Dei o nome de “MinhaPolitica” a essa autorização.

Então, minha primeira recomendação é preparar os serviços do Startup.cs para lerem o domínio – ou os domínios – que você quiser. Na parte de Configuração dos Serviços (ConfigureServices) inclua:

Configure Services
 public void ConfigureServices(IServiceCollection services)
 {
         //-seu conteúdo atual

        //-inclua DEPOIS de sservices.AddMvc();
   
        //detalhes em https://stackoverflow.com/questions/31942037/how-to-enable-cors-in-asp-net-core
            services.AddCors(options =>
            {
                options.AddPolicy("MinhaPolitica",
                    builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
            });
}

Configurando o serviço globalmente

Depois disso é preciso configurar o serviço. :

Configura&ccedil;&atilde;o Global
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, Alicecapella119Context context)
{
//- seu conteúdo atual
// - inclua se não quiser definir isso em todos os controladores que vão receber ou fornecer dados para os selects (ou outras finalidades)
//https://weblog.west-wind.com/posts/2016/Sep/26/ASPNET-Core-and-CORS-Gotchas
 // global policy - assign here or on each controller
 app.UseCors("MinhaPolitica");
}

Configurando Ações (Actions) dos Controladores (Controllers)

Se quiser garantir o acesso no seu próprio controlador (Control), coloque ANTES da ação do seu Controle (controller) o EnableCors :

Configura&ccedil;&atilde;o do Get no Action do Controller
 //para json
        [HttpGet]
        [EnableCors("MinhaPolitica")] // AQUI!!!
        public JsonResult PegaCompetencias(int Id)
        {

            List<SelectListItem>Lista = new List<SelectListItem>();
            var Dados = _context.CompetenciasBancoGeral
                    .Where(r => r.IdConsultoria == Id)
                    .OrderBy(r => r.TituloCompetencia)
                    .Select(r => new
                     { r.IdCompetenciaBanco, r.TituloCompetencia });
            foreach (var Linha in Dados)
            {
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdCompetenciaBanco.ToString(),
                    Text = Linha.TituloCompetencia,
                    Selected = false
                });
            }
            return Json(Lista);
        }

Configurações Diferentes para ambiente de Desenvolvimento e Produção

Pronto. Temos certeza agora que as chamadas GET, em Ajax, vão chegar. Lendo o artigo em referência, você verá que pode definir diferentes políticas, uma para ambiente de desenvolvimento, outra par ambiente de produção e, ao invés de liberar geral como fiz aqui, fazer liberações para localhost, seu domínio ou outros domínios, tendo mais de uma política. Exemplo:

Configura&ccedil;&otilde;es para Desenvolvimento e Produ&ccedil;&atilde;o
if (env.IsDevelopment())
            {
                 services.AddCors(options =>
                 {
                   options.AddPolicy("MinhaPoliticaLocal",
                    builder => builder.AllowAnyOrigin("localhost")
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
                 })
            }
            else
            {
                aservices.AddCors(options =>
                 {
                   options.AddPolicy("MinhaPoliticaRemota",
                    builder => builder.AllowAnyOrigin("meudominio.com.br")
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
                 })
            
            }

Princípio Básico dos Selects em Cascata

Selects em cascata funcionam do seguinte modo. Há um select MESTRE e um select ESCRAVO. Quando um item é selecionado no Select MESTRE, todos os itens do Select ESCRAVO são filtrados por uma variável (tipo ID) da opção selecionada. Itens sem essa ID são desprezados e somente são mostrados no Select ESCRAVO os itens que apresentam o mesmo valor da ID selecionada.

Ao fazermos uma seleção no MESTRE, essa escolha pode ser monitorada pelo comando Jquerychange()“:

Jquery change()
$(document).ready(function() {
	$('#SelectConsultoria').change(function() {
		// aqui vai acontecer o recarregamento dos dados do 
                //select ESCRAVO

		console.log("clicou select consultoria e mudou");

		}
	});

A rotina em Jquery para recarregar o Select ESCRAVO

Quando o  Jquerychange()” percebe uma mudança no select MESTRE (no exemplo pelo select com id=”SelectConsultoria”, a sequência de ações é a seguinte:

  1. Limpa o select escravo, cuja Id é “SelectCompetencia” (no modelo de dados, cada Consultoria opera um conjunto de Competências), com a função jquery empty(). Não use remove(), que apaga o select inteiro e não s opções…;
  2. Acrescenta uma única linha com a função append(), tipo “Selecione uma Competência”;
  3. Força um refresh (algumas versões de navegador ficam mortinhas). Você faz o append() e a tela não muda. Esse aqui é um truque, forçando o DOM a atualizar o elemento com id “SelectCompetencia”. Usa uma função local chamada “redraw”;
  4. Monta a URL usando @Context.Request.Host do CORE MVC. Muita gente usa @Url, comigo não funcionou. A Url já passa o parâmetro que foi escolhido no Select Mestre. O pulo do gato, aqui, é que esse endereço tem o CONTROLADOR (controller)e a AÇÃO (Action) que tem de ser chamada para devolver, em formato Json, os dados filtrados. Sim, ela vai buscar no controller do CORE MVC ;
  5. Faz a chamada Ajax com ‘GET’, para buscar os dados.;
  6. Com o sucesso da conexão, coloca os dados em ‘data’ e manda processar numa função à parte chamada callbackFunction;
  7. A função callbackFunction popula o Select ESCRAVO novamente, O pulo do gato, aqui, é que ela no final remove uma linha que ficaria em branco.
  8. Essas rotinas ficam no miolo do @section Scripts { }, no final da sua página .CSHTML. Não esqueça de colocar <script> e </script>, ficando @section Scripts { <script> //todas as suas rotinas </script> }
Todas rotinas Jquery
$(document).ready(function() {
	$('#SelectConsultoria').change(function() {
		console.log('@Context.Request.Host');
		console.log("clicou select consultoria e mudou");
        //limpa seletc ESCRAVO
		$('#SelectCompetencia').empty();
		console.log("passou por remoção da lista");
		
		//Guarda opção que foi selecionada no Select MESTRE
		var OpcaoAtual = $(this).val();
		$('#SelectCompetencia').append($('<option></option>').val("0").html('Selecione uma Competência'));
		var x = document.getElementById("SelectCompetencia");
		var option = document.createElement("option");
		
		//Adiciona uma opção tipo Slecione um Valor
		//option.value = "0"
		x.add(option);
		
		//Mancada para forçar redraw - pode não ser necessário
		var element = document.getElementById('SelectCompetencia');
		var n = document.createTextNode(' ');
		var disp = element.style.display; // don't worry about previous display style
		element.appendChild(n);
		element.style.display = 'none';
		setTimeout(function() {
			element.style.display = disp;
			n.parentNode.removeChild(n);
		}, 20); // you can play with this timeout to make it as short as possible
        //fim do redraw - este bloco pode ser retirado

        //para conferir a opção select MESTRE selecionada
		console.log("Opcao Atual" + OpcaoAtual);
		
		//monta URL completa, com parâmetro
		var url = 'http://' + '@Context.Request.Host/PerguntasBancoGeral/PegaCompetencias/' + OpcaoAtual;
		//verifica url montada
		console.log(url);
		console.log('idConsultoria = ' + $('#SelectConsultoria').val());
        
        //chama AJAX
		$.ajax({
			type: 'GET',
			headers: {
				'Accept': 'application/json',
				'Content-Type': 'text/plain'
			},
			dataType: 'json',
			url: url,
			success: function(data) {
				console.log(data);
				callbackFunction(data); //IMPORTANTE
			}
		});

        //Carrega o select ESCRAVO
		function callbackFunction(resultData) {
			// alert("entrei");
			var items = '<option value="0"><<Selecione uma Competência>></option>';
			//console.log(resultData);
			$.each(resultData, function(i, competencia) {

				items += "<option value='" + competencia.value + "'>" + competencia.text + "</option>";
				$("#SelectCompetencia").append("<option value='" + competencia.value + "'>" + competencia.text + "</option>");
				//confere no console
				console.log("<option value='" + competencia.value + "'>" + competencia.text + "</option>");

			});
			//confere no console
			console.log(items);
			//truque para tirar linha em branco
			$('#SelectCompetencia option:eq(' + 1 + ')').remove(); //retira branco
			
		}
	});

    //função auxiliar REDRAW
    //https://gist.github.com/mgHenry/6154611
	jQuery.fn.redraw = function() {
		jQuery(this).each(function() {
			this.style.display = 'none';
			this.offsetHeight; // no need to store this anywhere, the reference is enough
			this.style.display = 'block';
		});
	};
});

A Ação (Action) no Controlador (Controller)

Montei uma ação específica para o “GET” do Ajax, via Jquery. Antes da ação, defini que para um GET, com [HttpGet]. E também assinalei a política local [EnableCors)”MinhaPolitica”)], já mencionada.

Aqui é montada uma lista simples. Filtrada pela Id do Select mestre, passado via URL como parâmetro. Observe que todos os itens da lista são acrescentados com Selected = false, pois  o Select ESCRAVO ainda não tem nada selecionado (e foi o próprio Javascript que criou o primeiro elemento com “Selecione uma Competência” .

Se isso não tivesse sido feito lá, poderíamos fazer aqui logo depois de definir a nova lista, fazendo um Lista.Add de um único elemento ANTES de entrar no loop foreach.

Observe que o retorno é em formato Json. É isso. Tem muita coisa antiga e errada na internet. Não precisa acrescentar parâmetro nenhum. Funciona que é uma chupeta.

 

 

A A&ccedil;&atilde;o GET no Controlador
//para json
        [HttpGet]
        [EnableCors("MinhaPolitica")]
        public JsonResult PegaCompetencias(int Id)
        {

            List<SelectListItem> Lista = new List<SelectListItem>();
            var Dados = _context.CompetenciasBancoGeral
                    .Where(r => r.IdConsultoria == Id)
                    .OrderBy(r => r.TituloCompetencia)
                    .Select(r => new
                    { r.IdCompetenciaBanco, r.TituloCompetencia });
            foreach (var Linha in Dados)
            {
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdCompetenciaBanco.ToString(),
                    Text = Linha.TituloCompetencia,
                    Selected = false
                });
            }
            return Json(Lista);
        }
Configura&ccedil;&otilde;es para Desenvolvimento e Produ&ccedil;&atilde;o
if (env.IsDevelopment())
            {
                 services.AddCors(options =>
                 {
                   options.AddPolicy("MinhaPoliticaLocal",
                    builder => builder.AllowAnyOrigin("localhost")
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
                 })
            }
            else
            {
                aservices.AddCors(options =>
                 {
                   options.AddPolicy("MinhaPoliticaRemota",
                    builder => builder.AllowAnyOrigin("meudominio.com.br")
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .AllowCredentials());
                 })
            
            }

A View com os selects usando o Select Tag Helper

A view que foi montada tem 3 blocos:

  • Bloco 1: os selects MESTRE e ESCRAVO;
  • Bloco 2: uma seção informativa do resultado da seleção acima se o botão de filtrar é pressionado;
  • Bloco 3: um grid com o resultado da seleção.

DICA:

Habilitando Tag Helpers de forma genérica

Para que tag helpers funcionem em qualquer arquivo de visualização, a opção é habilitar no arquivo auxiliar de visualização que é sempre lido (o _ViewImports.cshtml) .

Assim, no arquivo  _ViewImports.cshtml, inclua:

Isso pode ser feito com outros tag helpers que você vier a construir (além dos padrões Microsoft já existentes no Visual Studio).

Bloco 1 – Selects Mestre e Escravo

cascading selects

O primeiro select é o MESTRE e o segundo é o ESCRAVO. O funcionamento com javascrpt já vimos. Ocorre que quando entramos na página, esses selects devem ser previamente populados.

Papa popular usamos ViewBags, seguindo a sintaxe basica do HTML TAG HELPER para selects:  os dados são carregados dinamicamente a partir de uma base de dados e no select são colocadas no tag asp-items, formato asp-items(“ViewBag.MeusDados)”. Par carregar as ViewBagas é preciso colocar  neles uma lista IEnumerable. A View, nesse ponto, é um simples formulário com dois selects com suas respectivas ViewBags.

C&oacute;digo da View: Formul&aacute;rio (Form) - P&aacute;gina Index.cshtml / Action Index no Controller
<form asp-action="Index2">
    <h2>Filtra Lista de Perguntas por Consultoria e por Competência</h2>
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="IdConsultoria" class="col-md-2 control-label">Consultoria</label>
        <div class="col-md-10">
            <select id="SelectConsultoria" asp-for="IdConsultoria" asp-items="ViewBag.Consultoria" class="form-control seletor"></select>
            <span asp-validation-for="IdConsultoria" class="text-danger"></span>
        </div>
    </div>
    <br /><br />
    <div class="form-group">
        <label asp-for="IdCompetenciaBanco" asp-items="" class="col-md-2 control-label">Competência</label>
        <div class="col-md-10" id="Competencias">
            <select asp-items="ViewBag.Competencia" id="SelectCompetencia" asp-for="IdCompetenciaBanco" class="form-control seletor"></select>
            <span asp-validation-for="IdCompetenciaBanco" class="text-danger"></span>
        </div>
    </div>
    <br /><br />
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10" align="left">
            <table>
                <tbody>
                    <tr>
                        <td><input type="submit" value="Filtra Perguntas" class="btn btn-primary" /> </td>
                        <td> </td>
                        <td><a asp-action="Index" class="btn btn-danger">Limpa Escolhas</a> </td>
                    </tr>
                </tbody>
            </table>
              <br />
        </div>
    </div>
</form>
Note que o form (formulário) é submetido para Index2, no controlador. Index captura a entrada (GET), Index2 captura o formulário quando é submetido (via POST).

Bloco 2 – Botão de Filtrar

O botão Filtra Perguntas, no código acima, é um simples botão de submit, que vai obedecer o encaminhamento para Index2, definida no topo do formulário (<form asp-action=”Index2″>).

O botão Limpa Escolhas é um link simples (<a>…</a>), não passa nenhum parâmetro. A ideia é essa, simplesmente recarregar a página sem nenhuma filtragem, mostrando as 10 últimas perguntas cadastradas sem qualquer filtragem.

cascading dropdown

Quando o botão Filtra Perguntas é clicado, na verdade ele passa para Index2 os dois Ids. Nenhum deles pode estar selecionado, somente um dos dois (ou um ou outro) ou os 2: temos 4 opções possíveis. A ação Index2 simplesmente pega as Ids do POST e “devolve a bola” para a ação Index, que alem de repopular os selects, também muda a mensagem sobre a filtragem e os contadores de Totais (de Consultorias, de Competências e de Perguntas). Além disso, filtra os dados da Grid que vai ser apresentada abaixo desse bloco, no bloco 3. Todas as informações são passadas via ViewBags que são instanciadas na Ação (action) Index do controlador.

O botão Cria Nova Pergunta leva para um formulário de inserção, já com os dois parâmetros escolhidos. Quando uma nova pergunta é gravada, o form (formulário) e perguntas cham novamente a página Index, mas já passando para ela os dois IDs que recebeu (de forma que se pode cadastrar várias perguntas de uma Consultoria, para uma mesma Competência, sem ter de ficar novamente fazendo Selects!!!).

Index.cshtml - Bloco de Informa&ccedil;&otilde;es atrav&eacute;s de ViewBags
<div class="row">
   <div class="col-sm-3">
      <div class="alert-danger" style="position:absolute; position:absolute;top:0;right:0;bottom:0">
         @ViewBag.MensagemAviso
         <span class="glyphicon glyphicon-alert"></span>
      </div>
   </div>
   <div class="col-sm-3">
      <div class="alert-info">
         Total de Consultorias : 
         <h3>@ViewBag.TotalDeConsultorias</h3>
      </div>
   </div>
   <div class="col-sm-3">
      <div class="alert-info">
         Total de Competências : 
         <h3>@ViewBag.TotalDeCompetencias</h3>
      </div>
   </div>
   <div class="col-sm-3">
      <div class="alert-info">
         Total de Perguntas: 
         <h3>@ViewBag.TotalDePerguntas</h3>
      </div>
   </div>
</div>
</div>

Bloco 3 – Grid com  Listagem das últimas perguntas registrado (máximo de 10)

Aqui a coisa pegou, porque o formulário (form) uma um modelo simples (não é uma lista!) e o grid precisa de uma lista… Os arquivos de Views  não permitem, de modo direto, que você defina dois ou mais modelos no topo (cabeçalho). É um modelo somente e ponto. Ir por esse caminho implica em gerar um terceiro modelo que contenha, dentro dele, os modelos que você quer usar na View. Mas pesquisando aqui e ali, acabei descobrindo uma outra forma de fazer isso, que será mostrada mais adiante.

cascading - formulário resultante do dropdown cascading

O modelo das Perguntas é muito simples:

 

Modelo das Perguntas (Model/Perguntas.cs)
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Coaching.Models
{
    public partial class PerguntasBancoGeral
    {
        [Key]
        [Display(Name = "Id da Pergunta do Banco")]
        public int IdPerguntaBanco { get; set; }

        [Required(ErrorMessage = "Favor escolher uma Consultoria.")]
        [Display(Name = "id da Consultoria")]
        public int IdConsultoria { get; set; }

        [Required(ErrorMessage = "Favor escolher uma Competência.")]
        [Display(Name = "Id da Competência do Banco")]
        public int IdCompetenciaBanco { get; set; }

        [Display(Name = "Assertiva Genérica (Hetero)")]
        public string Assertivageral { get; set; }

        [Display(Name = "Assertiva Auto")]
        public string Assertivaauto { get; set; }
    }
}
O modelo acima é definido no topo do arquivo da View:

@model Coaching.Models.PerguntasBancoGeral
@{
ViewData[“Title”] = “Banco de Perguntas – Coaching Adm”;
}

Só que, a Grid, me interessava listar o Nome da Consultoria e o Nome da Competência, que estão em outras tabelas. Montei um modelo FAKE, contendo também os nomes, para poder popular esse modelo “Lista” (IENUMERABLE) através da ação Index do controlador (controller).

E, dentro da View, na hora de LISTAR todas as perguntas, no comando foreach, chamei esse modelo fake com os nomes:
foreach (var item in ViewBag.Lista as IEnumerable<Coaching.Models.PerguntasBancoGeralComNome>)

Veja a seguir o modelo FAKE e o bloco de código HTML da VIew do bloco 3 (grid).

Modelo das Perguntas Na Grid (Model/PerguntasComNomes.cs) - Modelo "FAKE"
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace Coaching.Models
{
    public class PerguntasBancoGeralComNome //somente para visualização
    {
        [Key]
        [Display(Name = "Id da Pergunta do Banco")]
        public int IdPerguntaBanco { get; set; }

        [Display(Name = "id da Consultoria")]
        public int IdConsultoria { get; set; }

        [Display(Name = "Id da Competência do Banco")]
        public int IdCompetenciaBanco { get; set; }

        [Display(Name = "Assertiva Genérica (Hetero)")]
        public string Assertivageral { get; set; }

        [Display(Name = "Assertiva Auto")]
        public string Assertivaauto { get; set; }

        [Display(Name = "Nome da Consultoria")]
        public string NomeConsultoria { get; set; }

        [Display(Name = "Nome da Competência")]
        public string TituloCompetencia { get; set; }
    }
}
HTML da Grid - Bloco 3 - Index.cshtml - Modelo "FAKE" com Nomes (IEnumerable)
<div class="row">
   <div class="col-sm-12">
      <p>
         <a asp-action="Create" asp-route-idConsultoria="@ViewBag.idConsultoria" asp-route-IdCompetenciaBanco="@ViewBag.IdCompetenciaBanco" class="btn btn-primary">Cria Nova Pergunta</a>
      </p>
      <h2>@ViewBag.MensagemTotal</h2>
      @*10 últimas perguntas cadastradas<*@
      <table class="table table-responsive table-hover">
         <thead>
            <tr>
               <th>
                  @Html.DisplayNameFor(model => model.Assertivageral)
               </th>
               <th>
                  @Html.DisplayNameFor(model => model.Assertivaauto)
               </th>
               <th>
                  @*@Html.DisplayNameFor(model => model.IdCompetenciaBanco)*@
                  Id/Competências
               </th>
               <th>
                  Id/Consultoria
               </th>
               <th><b>Ação</b></th>
            </tr>
         </thead>
         <tbody>
            @{
            foreach (var item in ViewBag.Lista as IEnumerable
            <Coaching.Models.PerguntasBancoGeralComNome>
            )
            {
            <tr>
               <td>@item.Assertivageral</td>
               <td>@item.Assertivaauto</td>
               <td>
                  @item.IdCompetenciaBanco/@item.TituloCompetencia
               </td>
               <td>@item.IdConsultoria/@item.NomeConsultoria</td>
               <td>
                  <a asp-action="Edit" asp-route-id="@item.IdPerguntaBanco">Edita</a> |
                  <a asp-action="Details" asp-route-id="@item.IdPerguntaBanco">Detalhes</a> |
                  <a asp-action="Delete" asp-route-id="@item.IdPerguntaBanco">Apaga</a>
               </td>
            </tr>
            }
            }
         </tbody>
      </table>
   </div>
</div>

Populando as opções do Select através do Banco de Dados

Para fazer isso criei dois métodos PRIVADOS dentro do controlador (controller). Um para popular a ViewBag de Consultorias, outro para popular a ViewBag de Competências.

M&eacute;todo Privado para Popular Consultorias - arquivo controlador (controller)
private void PopulateConsultoria(int TipoSelecionado = 0)
        {
            List<SelectListItem> Lista = new List<SelectListItem>();
            if (TipoSelecionado == 0)
            {
                Lista.Add(new SelectListItem()
                { Value = "0", Text = "<<Selecione uma Consultoria>>", Selected = true });
            }
else
                  { Value = "0", Text = "<<Selecione uma Consultoria>>", Selected = false});
            }
            var Dados = _context.Consultorias
                .OrderBy(r => r.NomeConsultoria)
                .Select(r => new
                { r.IdConsultoria, r.NomeConsultoria });
            foreach (var Linha in Dados)
            {
                bool valor = false;
                if (Linha.IdConsultoria == TipoSelecionado)
                {
                    valor = true;
                }
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdConsultoria.ToString(),
                    Text = Linha.NomeConsultoria,
                    Selected = valor
                });
            }
            ViewBag.Consultoria = Lista;
        }
Note que o VIewBag é instanciado dentro do método PRIVADO. Outra coisa: atribui o valor zero (0) para o “Selecione uma Consultoria”, pois isso facilita muito saber se a pessoa não fez nenhuma opção no select. É melhor que trabalhar com ”nulls”.

Se houver um item selecionado, vem um valor diferente de zero, que é passado por TipoSelecionado. A linha, em dados, cuja Id “bater” com o valor de TipoSelecionado será marcada como verdadeira (Selected = true).  Assim, quando a ViewBag entregar essa lista para o Select, o mesmo estar´posicionado nesta opção. DO contrário fica na primeira opção (0), “Selecione uma Consultoria”.

Populando as Competências

O princípio desse método é exatamente igual ao do anterior. A diferença é que ele utiliza 2 parâmetros que podem ter 4 comninações possíveis:

  • os 2 não são passados (são zero), nenhum dos dois foi selecionado: mostra todas as opções existentes (todas as competências);
  • somente o Id da Competência foi passado: mostra exatamente essa competência;
  • somente o id da Consultoria foi passado: mostra todas as competências ligadas a essa consultoria
  • os 2 são passados (diferente de zero): mostra exatamente a competência específica (igual caso 2)

O resultado dessa lista é armazenado na ViewBag de Competências dentro do método privado.

M&eacute;todo Privado para Popular Compet&ecirc;ncias - arquivo controlador (controller)
private void PopulateCompetencia(int IdCompetenciaLida = 0, int idConsultoriaLido = 0)
        {
            List<SelectListItem> Lista = new List<SelectListItem>();
            var Comp = 0;
            var Cons = 0;
            if (IdCompetenciaLida >= 1)
            {
                Comp = 1;
            }
            if (idConsultoriaLido >= 1)
            {
                Cons = 1;
            }
            if (IdCompetenciaLida == 0)
            {
                Lista.Add(new SelectListItem()
                { Value = "0", Text = "<<Selecione uma Competência>>", Selected = true });
            }
            var Dados = (from i in _context.CompetenciasBancoGeral
                             //join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                         where (Cons == 1 && i.IdConsultoria == idConsultoriaLido) || (Comp == 1 && i.IdCompetenciaBanco == IdCompetenciaLida)
                         || (Comp == 0 && Cons == 0)
                         select new CompetenciasBancoGeral
                         {
                             IdCompetenciaBanco = i.IdCompetenciaBanco,
                             TituloCompetencia = i.TituloCompetencia
                         })
                         .OrderBy(p => p.TituloCompetencia)
                         .ToList();
            foreach (var Linha in Dados)
            {
                bool valor = false;
                if (Linha.IdCompetenciaBanco == IdCompetenciaLida)
                {
                    valor = true;
                }
                Lista.Add(new SelectListItem()
                {
                    Value = Linha.IdCompetenciaBanco.ToString(),
                    Text = Linha.TituloCompetencia,
                    Selected = valor
                });
            }
            ViewBag.Competencia = Lista;
        }

A Ação (Action) Index do Controlador

Esta ação é o “coração” desta pequena aplicação. Quando se entra na pagina Index.cshtml, a ação Index é solicitada no controlador (controller) e ela executa 3 papéis importantes:

  • chama os dois métodos populadores, passando a eles os parâmetros escolhidos na View;
  • carrega uma lista para a grid do modelo fake, com as devidas filtragens dos parâmetros e a coloca também numa VIeBag;
  • calcula e define todas as demais ViewBags que serão mostradas no arquivo de visualização (View), considerando as 4 possíveis alternativas de passagem dos 2 parâmetros.

O código é mostrado a seguir.

C&oacute;digo da A&ccedil;&atilde;o Index do Controlador (controller)
        // GET: PerguntasBancoGeral
        public async Task<IActionResult> Index(int idConsultoria = 0, int IdCompetenciaBanco = 0)
        {
            //id1 -id Consultoria
            //id2 - id Competencia
            var id1 = 0;
            var id2 = 0;
            var pConsultoria = 0;
            var pCompetencia = 0;
            if (idConsultoria >= 1)
            {
                id1 = 1;
                pConsultoria = idConsultoria;
            }
            if (IdCompetenciaBanco >= 1)
            {
                id2 = 1;
                pCompetencia = IdCompetenciaBanco;
            }
            PopulateConsultoria(pConsultoria);
            PopulateCompetencia(pCompetencia, pConsultoria);
            ViewBag.Lista = await (from i in _context.PerguntasBancoGeral
                                   join k in _context.Consultorias on i.IdConsultoria equals k.IdConsultoria
                                   join m in _context.CompetenciasBancoGeral on i.IdCompetenciaBanco equals m.IdCompetenciaBanco
                                   where (
                                   (id1 == 0 && id2 == 0)
                                   ||
                                   (id1 == 0 && id2 == 1 && i.IdCompetenciaBanco == pCompetencia)
                                   ||
                                   (id1 == 1 && id2 == 0 && i.IdConsultoria == pConsultoria)
                                   ||
                                   (id1 == 1 && id2 == 1 && i.IdConsultoria == pConsultoria && i.IdCompetenciaBanco == pCompetencia)
                                   )
                                   select new PerguntasBancoGeralComNome
                                   {
                                       Assertivaauto = i.Assertivaauto,
                                       Assertivageral = i.Assertivageral,
                                       IdCompetenciaBanco = i.IdCompetenciaBanco,
                                       IdPerguntaBanco = i.IdPerguntaBanco,
                                       IdConsultoria = k.IdConsultoria,
                                       NomeConsultoria = k.NomeConsultoria,
                                       TituloCompetencia = m.TituloCompetencia
                                   })
                                   .OrderByDescending(p => p.IdPerguntaBanco)
                                   .Take(10) //pega as 10 últimas
                                   .ToListAsync();
            if (pConsultoria == 0 && pCompetencia == 0)
            {
                ViewBag.MensagemAviso = "Ainda sem filtragem";
                ViewBag.TotalDeConsultorias = await _context.Consultorias.CountAsync();
                ViewBag.TotalDeCompetencias = await _context.CompetenciasBancoGeral.CountAsync();
                ViewBag.TotalDePerguntas = await _context.PerguntasBancoGeral.CountAsync();
               
            }
            else if (pConsultoria >= 1 && pCompetencia == 0)
            {
                ViewBag.MensagemAviso = "Consultoria Definida";
                ViewBag.TotalDeConsultorias = await _context.Consultorias.Where(x => x.IdConsultoria == pConsultoria).CountAsync();
                ViewBag.TotalDeCompetencias = await _context.CompetenciasBancoGeral.Where(x => x.IdConsultoria == pConsultoria).CountAsync();
                ViewBag.TotalDePerguntas = await _context.PerguntasBancoGeral.Where(x => x.IdConsultoria == pConsultoria).CountAsync();
            }
            else if (pConsultoria >= 1 && pCompetencia >= 1)
            {
                ViewBag.MensagemAviso = "Consultoria Definida e Competência Definida: Clique em CRIA NOVA PERGUNTA";
                ViewBag.TotalDeConsultorias = await _context.Consultorias.Where(x => x.IdConsultoria == pConsultoria).CountAsync();
                ViewBag.TotalDeCompetencias = await _context.CompetenciasBancoGeral.Where(x => x.IdConsultoria == pConsultoria && x.IdCompetenciaBanco == pCompetencia).CountAsync();
                ViewBag.TotalDePerguntas = await _context.PerguntasBancoGeral.Where(x => x.IdConsultoria == pConsultoria && x.IdCompetenciaBanco == pCompetencia).CountAsync();
            }
            else // else if (pConsultoria = 0 1 && pCompetencia >= 1)
            {
                ViewBag.MensagemAviso = "Competência Definida: falta definir consultoria";
                ViewBag.TotalDeConsultorias = await _context.Consultorias.CountAsync();
                ViewBag.TotalDeCompetencias = await _context.CompetenciasBancoGeral.Where(x => x.IdCompetenciaBanco == pCompetencia).CountAsync();
                ViewBag.TotalDePerguntas = await _context.PerguntasBancoGeral.Where(x => x.IdCompetenciaBanco == pCompetencia).CountAsync();
            }
            if (ViewBag.TotalDePerguntas >= 10) { ViewBag.MensagemTotal = "10 últimas perguntas cadastradas"; }
            else if (ViewBag.TotalDePerguntas >= 1 && ViewBag.TotalDePerguntas <= 9) { ViewBag.MensagemTotal = Convert.ToString(ViewBag.TotalDePerguntas) + " últimas perguntas cadastradas"; }
            else { ViewBag.MensagemTotal = "Nenhuma pergunta cadastrada ainda (nesta filtragem)"; }
            ViewBag.idConsultoria = idConsultoria;
            ViewBag.IdCompetenciaBanco = IdCompetenciaBanco;
            return View();
        }

Obrigado pela sua leitura. Continue visitando este blog e compartilhe artigos em sua rede de relacionamento. Por favor, se quiser, registre sugestões e comentários ao final da página.