Escolha uma Página
Usando editor HTML no Core MVC, com Bootstrap e Summernote

Usando editor HTML no Core MVC, com Bootstrap e Summernote

Usando editor HTML no Core MVC, com Bootstrap e Summernote

De todas as tentativas que já fiz, o uso do Summernote dentro do Core MVC é a solução mais simples para editar HTML em views, A solução é leve, funciona muito bem e é compatível com Bootstrap 4.

Para que eu uso o Editor de HTML

O editor é muito útil para mim para:

  • Montar páginas de Ajuda (help) para diversos pontos do meu sistema;
  • Guardar Mensagens padronizadas do sistema, para envio por e-mail.

A manutenção fica estremamaente simplificada.

O meu arquivo tem uma estrutura muito simples: identidade (id), assunto e mensagem.

Coloque na pasta Models:

Arquivo AjudasMensagens.cs na pasta Models.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace PoupaTempoDigital.Models
{
    public class AjudasMensagens
    {
        [Key]
        [Display(Name = "Id da Ajuda/Mensagem")]
        public int IdAjudaMensagem { get; set; }

        [Display(Name = "Assunto da Ajuda/Mensagem")]
        [MaxLength(200)]
        public string Assunto { get; set; }

        [Display(Name = "Corpo da Ajuda/Mensagem")]

        public string TextoAjudamensagem { get; set; }

    }
}

Dê Buidl/Rebuild Solution pelo menu superior.

Vá na pasta controller com o botão direito do mouse escolha Add->Add New Scafolded Item.

Escolha a opção MVC Controller with Views, using Entity Framework. Clicks ADD.

Escolha o modelo e o contexto, deixe ticadas as opçõesde layout.

Clicks em ADD para criar o arquivo controlador AjudasMensagensController.cs e as vies de Edicação, na pasta VIEWS/AjudasMensagens (index (listagem), create, update e delete (todas .cshtml).

Para gerar a tabela no seu banco de dados, na janela PM digite:

add-gration AjudasMensagens

update-database

Instalando o Summernote

Baixando o cógido fonte

Obtenha o último Summernote LESS e o código-fonte Javascript baixando-o diretamente do GitHub.  Coloque os arquivos debaixo da sua pasta WWW. Abra d pasta summernote e copie os arquivos que estão na pasta src do GitHub (contém icons, js and styles, ou seja, css).

O que o Summernote tem de bom?

O Summernote tem diversos recursos especiais:

  • Colar imagens diretamente da área de transferência
  • Salvar imagens diretamente no conteúdo do campo, usando codificação base64, para que você não precise implementar o manuseio de imagens em todas as interfaces de usuário
  • User Interface (UI) simples
  • Edição interativa WYSIWYG 
  • Integração facilitada com o servidor
  • Suporta Bootstrap 3 Bootstrap 4
  • Muitos plug-ins e conectores fornecidos juntos

Instalação e Dependências

 O Summernote depende do JQUERY. Os arquivos CSS e JS tem que ser instados depois dos arquivos de jquery e depois do bootstrap entre <HEAD> e </HEAD>. Requer HTML5 e, no caso Brasil, no arquivo Views?shared/_layout,cshtml, você tem que ter:

Cabecalho do Arquivo _layout.cshtm na pasta Views/Shared
<!DOCTYPE html>
<html lang="pt-BR">
...
</html>

O seu código no arquivo _layout.cshtml vai ficar algo parecido com isso (a sequência é muito importante):

Links do Cabecalho do Arquivo _layout.cshtm na pasta Views/Shared
<!-- include libraries(jQuery, bootstrap) -->
<link href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>

<!-- include summernote css/js -->
<link href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>

Ou ainda, se não for CDN

Links do Cabecalho do Arquivo _layout.cshtm na pasta Views/Shared
<!-- include libraries(jQuery, bootstrap) -->
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" />
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>

<!-- include summernote css/js-->
<link href="summernote.css" rel="stylesheet">
<script src="summernote.js"></script>

No meu caso, eu uso Bootstrap 4 – e não 3,. Não tente usar CDN no começo. Baixe o ZIP direto do site em https://summernote.org/getting-started/ e installe tudo no diretrório www/summernote.  (a vers]ao atual é o.82). Outra coisa: o exemplo do site esta ERRADO. pois no arquivo _layout.cshtml  o arquivo JQUERY fica no rodapé. Então, o arquivo js do summernote tem que ficar no RODAPE. Meu cabeçaho HTML ficou assim:

Links do Cabecalho do Arquivo _layout.cshtm na pasta Views/Shared
<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - PoupaTempo Digital do Empreendedor MEI</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link href="~/lib/font-awesome/css/all.css" rel="stylesheet" />
    <link href="~/lib/bootstrap-icons/bootstrap-icons.css" rel="stylesheet" />

    <!-- include summernote css/js -->
    <link href="~/summernote/summernote-bs4.css" rel="stylesheet" />
    

    <!-- Syncfusion Essential JS 2 Styles -->
    <link rel="stylesheet" href="https://cdn.syncfusion.com/ej2/material.css" />

    <!-- Syncfusion Essential JS 2 Scripts -->
    <script src="https://cdn.syncfusion.com/ej2/dist/ej2.min.js"></script>
    <link href="~/lib/font-awesome/css/all.css" rel="stylesheet" />

    <link rel="stylesheet" href="~/css/site.css" />
</head>
Links do rodape do Arquivo _layout.cshtm na pasta Views/Shared
 </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/js/inputmask/jquery.inputmask.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/lib/font-awesome/js/all.js"></script>

    <!-- include summernote css/js -->

    <script src="~/summernote/summernote-bs4.js"></script>


    <script src="~/js/site.js" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
    <!-- Syncfusion Essential JS 2 ScriptManager -->
    <ejs-scripts></ejs-scripts>
</body>
</html>

Inserindo o Editor de textos HTML

O Summernote pode ser usado com ou sem um formulário. Para usar sem um formulário, sugerimos usar um div no corpo; este elemento será então usado onde você deseja que o editor Summernote seja renderizado em sua página.

<div id = “summernote”> Olá Summernote </div>

Para usar com um formulário, é praticamente o mesmo que acima, mas em vez de usas uma div, recomendamos o uso de um elemento textarea dentro do formulário,

O textarea deve incluir um atributo de nome (ID) para que, quando o formulário for enviado, você possa usar esse nome para processar o dados dos editores em seu back-end.

Além disso, se estiver usando Summernote dentro de um formulário para definir o atributo method = “post” para permitir que o conteúdo do editor de tamanho maior analise para o back-end, se você não fizer isso, seus dados podem não ser analisados ou serão truncados.

<form method = “post”>

<textarea id = “summernote” name = “editordata”> </textarea>

</form>

Nota: se sua aplicação tiver mais que uma área de texto por view, pode usar summernote1, summernote2… etc. como IDs.

Rodando o Summernote

Quem roda o Summernote é o JQuery, quando o documento está pronto:

Quem roda o Summernote!
$(document).ready(function() {
  $('#summernote').summernote();
});

Para ter essa funcionalidade disponível em qualquer View, coloco este códico javasvript no final do arquivo _layout.cshtml.

Inclusive já preparando para páginas que possam ter até 3 visualizações do Summernote, o arquivo de _layout.cshtml é o que renderiza todas as páginas de visualização.

Os parâmetros de altura mínima e a altura parecem não funcionar mais, pois a versão em Bootstrap ficou esperta: abre a altura mínima e vai crescendo a altura à medida que o conteúdo vai crescendo. Bem inteligente.

A tradução em português, pt-BR, funciona bem nesta versão – e deve ser incluida depois do arquivo summernote.js., fo rodapé dapágina _layout.cshtml.

Final da pagina _layout.cshtml , em Views/Shared
....
</body>
</html>
<script>
$(document).ready(function () {


    if (typeof $("#Editar") !== "undefined") {
        $('#summernote').summernote({
            lang: 'pt-BR', // default: 'en-US'
            dialogsInBody: true,
            height: 600,                 // set editor height
            minHeight: 400,             // set minimum height of editor
            maxHeight: null,             // set maximum height of editor
            focus: true                 // set focus to editable area after initializing summernote
          
        });
        console.log(document.title); // para ver se está rodando direitinho
    }
});

$(document).ready(function () {
    if (typeof $("#Editar") !== "undefined") {
        console.log(document.title);
        $('#summernote2').summernote({
            lang: 'pt-BR', // default: 'en-US'
            dialogsInBody: true,
            height: 600,                 // set editor height
            minHeight: 400,             // set minimum height of editor
            maxHeight: null,             // set maximum height of editor
            focus: true                 // set focus to editable area after initializing summernote
        });
    }
});


$(document).ready(function () {
    if (typeof $("#Editar") !== "undefined") {
        console.log(document.title);
        $('#summernote3').summernote({
            lang: 'pt-BR', // default: 'en-US'
            dialogsInBody: true,
            height: 600,                 // set editor height
            minHeight: 400,             // set minimum height of editor
            maxHeight: null,             // set maximum height of editor
            focus: true                // set focus to editable area after initializing summernote
        });
    }
});
</script>
Sequencia de inclusao de arquivos na pagina _layout.cshtml , em Views/Shared
<footer class="border-top footer text-muted">
        <div class="container">
            © 2021 - PoupaTempo Digital do Empreendedor MEI - <a asp-area="" asp-controller="Home" asp-action="Privacy">Política de Privacidade</a> | <a asp-area="" asp-controller="Home" asp-action="TermosDeUso">Termos de Uso</a>
        </div>
    </footer>
    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/js/inputmask/jquery.inputmask.js"></script>
   
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/lib/font-awesome/js/all.js"></script>

    <!-- include summernote css/js -->
    <script src="~/summernote/summernote-bs4.js"></script>

    <script src="~/js/site.js" asp-append-version="true"></script>
    @RenderSection("Scripts", required: false)
    <!-- Syncfusion Essential JS 2 ScriptManager -->
    <ejs-scripts></ejs-scripts>

    <!-- include pt-BR js ATENCAO!!!!! -->
    <script src="~/summernote/lang/summernote-pt-BR.min.js"></script>
</body>
</html>
<script>
....
</script>

Em todas as páginas que quero usar o Summernote acrescento uma “div” em vazio, logo no topo da página.

Para usar em toda e qualquer pagina de edicao ou de criacao que vai usar o Summernote
<div id="Editar" title="sim"></div>

Exemplo de página, no caso, de criação, no diretório Views/AjudasMensagens. Não exquece de trocar INPUT por TEXTAREA e colocar id=”summernote”

Aquivo Create.cshtml em Views/AjudasMensagens
@model PoupaTempoDigital.Models.AjudasMensagens

@{
    ViewData["Title"] = "Create";
}
<div id="Editar" title="sim"></div>
<h1>Create</h1>

<h4>AjudasMensagens</h4>
<hr />
<div class="row">
    <div class="col-md-8">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Assunto" class="control-label"></label>
                <input asp-for="Assunto" class="form-control" />
                <span asp-validation-for="Assunto" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="TextoAjudamensagem" class="control-label"></label>
                @*<input asp-for="TextoAjudamensagem" class="form-control" />*@
                <textarea asp-for="TextoAjudamensagem" id="summernote" rows="20 "class="form-control"></textarea>
                <span asp-validation-for="TextoAjudamensagem" class="text-danger"></span>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

<div>
    <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Referências:

  1. Summernote

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 aqui no final da página.

Adicionando serviço de E-mail no Core MVC, usando SendGrid

Adicionando serviço de E-mail no Core MVC, usando SendGrid

Eu costumava usar o Mailkit para fazer o serviço de E-mail no Core MVC. Depois de algumas atualizações para o Core 5.X, o Mailkit começou a falhar. Mudei para o SendGrid: muito mais simples de implementar e o que é importante: não falha!

Vamos iimplentar oe-mail como um serviõs, que pode ser usado a partir de qualquer página de controle.

Abra uma pasta nova chamando-a de Services. Nela colocaremos 4 arquivos, como segue:

Classe EmailConfig - arquivo EmailConfig.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace PoupaTempoDigital.Services
{
    public class EmailConfig
    {
        public String FromName { get; set; }
        public String FromAddress { get; set; }

        public String LocalDomain { get; set; }

        public String MailServerAddress { get; set; }
        public String MailServerPort { get; set; }

        public String UserId { get; set; }
        public String UserPassword { get; set; }
   
    }
}

O próximo arquivo de classe é usado normalmente por Identity, que envia e-mails  para confirmação da conta e redefinição de senha. Para obter mais detalhes, consulte https://go.microsoft.com/fwlink/?LinkID=532713

Classe EmailSender - arquivo EmailSender.cs
using System.Threading.Tasks;

namespace PoupaTempoDigital.Services
{
    // This class is used by the application to send email for account confirmation and password reset.
    // For more details see https://go.microsoft.com/fwlink/?LinkID=532713
    public class EmailSender : IEmailSender
    {
        public Task SendEmailAsync(string email, string subject, string message)
        {
            return Task.CompletedTask;
        }
    }
}

O próximo arquivo é uma interface, e é o que eu normalmente uso, porque gosto de enviar na solicitação do e-mail uma string definindo se minha mensagem está em formato de texto ou está em HTML. Esse parametro é passado pela string formato.

Interface IEmailService - arquivo IEmailService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace PoupaTempoDigital.Services
{
    public interface IEmailService
    {
        Task SendEmailAsync(string email, string subject, string message, string formato);
    }

    public interface IEmailSender
    {
        Task SendEmailAsync(string email, string subject, string message);
    }

}

O quarto e último arquivo, nas pasta Services, é o arquivo que faz o serviço de entrega, usando o SendGrid. 

Você vai precisar instalar via Package Manager os componentes do SendGrid para validar o que é chamado no arquivo:
using SendGrid;
using SendGrid.Helpers.Mail;

Arquivo MessageServices.cs na pasta Services
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Configuration;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace PoupaTempoDigital.Services
{
    // source http://6figuredev.com/technology/generic-repository-dependency-injection-with-net-core/

    // https://www.codeproject.com/Articles/1166364/Send-email-with-Net-Core-using-Dependency-Injectio
    public class EmailService : IEmailService
    {
        private readonly EmailConfig ec;
        private readonly IConfiguration _configuration;

        public EmailService(
            IOptions<EmailConfig> emailConfig,
            IConfiguration configuration
            )
        {
            this.ec = emailConfig.Value;
            _configuration = configuration;
        }

        public async Task SendEmailAsync(String email, String subject, String message, String formato = "Html")
        {
            try
            {
                var API = _configuration.GetSection("Sendgrid").GetSection("ApiKey").Value;
                var client = new SendGridClient(API);
                var from = new EmailAddress(ec.FromAddress, ec.FromName);
                var assunto = subject;
                var to = new EmailAddress(email, "");//todo colocar nome de quem recebe a mensagem
                var htmlContent = "";
                var plainTextContent = "";
                if (formato != "Text" || formato != "Texto")
                {
                    htmlContent = "<html><body>" + message + "</body></html>"; //o summernote retira estas tags quando grava o HTML, eu reincluo para os e-mails
                }
                else
                {
                    plainTextContent = message; 
                }
                var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
              //opcional
                msg.AddBcc("email_1_copia_oculta@gmail.com", "[CÓPIA] Poupatempo Digital");
              //opcional 2
                msg.AddBcc("email_2_copia_oculta@gmail.com", "[CÓPIA] Poupatempo Digital");
                // Disable click tracking.
                // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
                msg.SetClickTracking(false, false);
                
              // disable tracking settings
                // ref.: https://sendgrid.com/docs/User_Guide/Settings/tracking.html
                msg.SetClickTracking(false, false);
                msg.SetOpenTracking(false);
                msg.SetGoogleAnalytics(false);
                msg.SetSubscriptionTracking(false);

                var response = await client.SendEmailAsync(msg).ConfigureAwait(false);

            }
            catch (Exception ex)
            {
                Console.Write(ex.Message);
            }
        }


    }
}

Note que a chave de API do Sendgrid está no arquivo de cofigurações (appsettings.json, no diretório principal da aplicação, que você vai precisar editar), Seção Sendgrid, subseção ApiKey.

A seção Email contém todos os dados do e-mail que deverá ter o domínio VALIDADO numa conta do SendGrid.

Edite seu arquivo appsettings.json na raiz do seu aplicativo Core MVC
{
  "Email": {
    "FromName": "Nome do seu Site",
    "FromAddress": "nomedoseuemail@dominiodoseusite.com.br",
    "LocalDomain": "dominiodoseusite.com.br",
    "MailServerAddress": "mail.dominiodoseusite.com.br",
    "MailServerPort": "8889", //PRECISA COLOCAR A PORTA QUE TEM A VER COM SEU SITE
    "UserId": "atendimento@dominiodoseusite.com.br",
    "UserPassword": "senha_do_seu_email"
  },
  "Sendgrid": {
    "ApiKey": "Chave_de_api_copiada_do_SendGrid"
  },

//outras configurações que você já tenha

  "ConnectionStrings": {
    "DefaultConnection": "string de conexão do seu banco de dados"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Precisamos agora injetar esse serviço no programa principal. Abra seu arquivo Startup.cs

Dentro da rotina 

public void ConfigureServices(IServiceCollection services)

{…}

acrescente o registro do seu serviço de e-mail:

Edite seu arquivo Startup.cs , colocando estas linha no final da rotina public void ConfigureServices(IServiceCollection services)
public void ConfigureServices(IServiceCollection services)
{
//... cortado por brevidade... deixe todo seu códiso original aqui

//https://www.codeproject.com/Articles/1166364/Send-email-with-Net-Core-using-Dependency-Injectio
            // Inject email service 
            // Register email service
            services.Configure<EmailConfig>(Configuration.GetSection("Email"));
            services.AddTransient<IEmailService, EmailService>();
}

Não se esqueça de acrescentar no topo do Startup.cs os arquivos de Modelos (Models) e de Serviços (Services). alterando o namespace para o do seu aplicativo.

Edite seu arquivo Startup.cs , acertando os usings
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using PoupaTempoDigital.Data;
using System;
using PoupaTempoDigital.Models; //MUITO IMPORTANETE
using PoupaTempoDigital.Services; //MUITO IMPORTANETE
using System.Globalization;
using Microsoft.AspNetCore.Http;

É o Sendgrid que envia seus e-mails na verdade.No plano grátis, você pode enviar até 12 mil e-mais por mês, uma média de 400 por dia (para mim é até demais).

Entre em E-mail API -> Integration Guide -> escolha a opção WEB API (Recomended). -> Escolha C#

No item 2, de um nome paa sua chave de API (API Key) e clique no botão CREATE KEY

Copie esta chave para seu arquivo appsettings.json

No final da página, do lado direito, clique no quase INVISIVEL radio button: ⊗  

Isso vai liberar o botão NEXT: VERIFY INTEGRATION

Duas últimas dicas:

  • não envie um e-mail para o próprio e-mail ou para um e-mail que este em CC ou BCC. Vai falhar, segundo o SendGrid.
  • a documentação do SendGrid permite que você, mesmo mandando o e-mail pelos servidores dela, apareçam para os servidores qque recebem seus e-mails por um CNAME do seu domínio. Com isso, a entregabilidade é muito maior e seus e-mails não vão parar na caixa de SPAM.

 

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 aqui no final da página.

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

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

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

Por menos de U$3/mês você pode ter sua aplicação ASP.NET Core MVC 5.X 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 5.X 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 5.X 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 aqui no 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 aqui no 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 aqui no final da página.