Escolha uma Página
Google Captcha (reCaptcha) no Core MVC

Google Captcha (reCaptcha) no Core MVC

O Captcha ajuda a evitar que robôs tentem invadir seus sites através de formulários, passando a ser mais uma pedra do caminho de invasões automatizadas. Quem tenta acessar o sistema usando o formulário passa a ter que também (pelo menos) clicar num botão dizendo: “Não sou um robô”.

O Google oferece esse serviço. Chamando-o de reCaptcha. Que pode ser usado com Core MVC.

Se você tiver algumas visualizações com entradas com acesso público, especialmente se forem servidas por meio do controlador com [AllowAnonymous], então provavelmente gostará de adicionar CAPTCHA a essas visualizações. Caso contrário, você sempre corre o risco de ser inundado por spam automatizado e até ser invadido.

Como todo serviço do Google, você precisar ter uma conta de Gmail para acessar o serviço em: https://www.google.com/recaptcha/admin

O site é bem confuso e muito pouco intuitivo. Você nunca sabe por onde começar. Se clicar no topo, vai ler:

O reCAPTCHA usa um mecanismo avançado de análise de risco e desafios adaptativos para impedir que o software malicioso se envolva em atividades abusivas no seu site. Enquanto isso, usuários legítimos poderão fazer login, fazer compras, visualizar páginas ou criar contas e usuários falsos serão bloqueados.

Tem de cllicar,esmo é no sinal de +, para adicionar a URL do seu site.

 

Coloque um nome para lembrar de que site é o reCaptcha, em etiqueta. Escolha a última versão: recaptcha v2. Defina o seu domínio. Importante: clique no botão de + depois que acrescentar seu domínio – e defina também localhost para depois poder testat o reCapcha na sua máquina local, ANTES de subir a sua aplicação para o servidor.

Duas dicas importantes:

  • Além de localhost, defina também 127.0.0.1 (verifique se o IIS do seu windows está ativado como um recurso do windows, se não estiver, ative);
  • Escolha a versão V2 do recapcha. Com a V3 não funcionou de jeito nenhum, nem na máquina local, nem remota,.

O proprietário aparece como seu e-mail do Gmail, mas você pode acrescentar outros (nunca tentei). Aceite os termos do serviço. Clique em enviar, no finalzinho da página.

Pronto, você tem agora os dados do seu recaptcha (sugiro guardar num arquivo txt, no seu micro).

Integração com Core Mvc

Para ter a possibilidade de usar o captcha em qualquer formulário, de qualquer página, é melhor chamar a API do Google do arquivo _layout.cshtml, em Views/Shared, uma linha acima de </head>. no topo da página.

 

chamada da API do Google em todas paginas no arquivo_layout.cshtml, em views/shared

    <link rel="stylesheet" href="~/css/site.css" />
    <script src="https://www.google.com/recaptcha/api.js"></script>
</head>
<body data-rsssl=1 data-rsssl=1 data-rsssl=1>

Outra possibilidade é colocar no final de cada View que terá o formulário com reCaptcha, no final da página, na section de script:

colocar em cada view com recaptcha no final da pagina
@section Scripts {
    <script src='https://www.google.com/recaptcha/api.js'></script>
}

Onde colocar o reCaptcha?

Em qualquer formuário que você quiser. Nas views, será necessário usar a “Chave do Site”, a site key que o Google nos forneceu. Calma, já veremos onde ela será guardada. No momento vamos assumir que ela está guardadinha em ViewBag,ChavedoSite.

Assim, em qualquer formulário onde você quiser usar o reCaptcha, vai ter de colocar antes de </form> o seguinte código:

Onde colocar o reCaptcha?

Em qualquer formuário que você quiser. Nas views, será necessário usar a “Chave do Site”, a site key que o Google nos forneceu. Calma, já veremos onde ela será guardada. No momento vamos assumir que ela está guardadinha em ViewBag.ChavedoSite.

Assim, em qualquer formulário onde você quiser usar o reCaptcha, vai ter de colocar antes de </form> o seguinte código:

linha a ser colocada antes de qualquer submit de formularios com reCaptcha
<div class="g-recapcha" data-siteky="@ViewBag.ChavedoSite">
</div>

Exemplo simplificado

Escolha qualquer formulário. Em geral o formulário está numa página de Create.cshtml se foi criado com scafolding, Neste caso, também deveria colocar o reCaptcha na página de edição, em geral Edit.cshtml.

exemplo simplificado para ser colocado nos formularios de criacao e edicao, Create.cshtml e Edit.cshtml
@model PoupaTempoDigital.Models.TP_BancosCaixas

@{
    ViewData["Title"] = "Cria Banco ou Caixa";
}

<h4>Cria Banco ou Caixa</h4>

<h5>Dados</h5>
<hr />
<div class="row">
    <div class="col-md-4">
        <form asp-action="Create">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="NomeBancoCaixa" class="control-label"></label>
                <input asp-for="NomeBancoCaixa" class="form-control" />
                <span asp-validation-for="NomeBancoCaixa" class="text-danger"></span>
              
        			<div class="g-recapcha" data-sitekey="@ViewBag.ChavedoSite">
</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");}
}

Onde guardar as chaves?

Eu proponho guardar no arquivo appsettings. json, no diretório raiz do site, assim:

chaves do google reCaptcha no appsettings.json
{
  "Email": {
    "FromName": "Site Poupatempo Digital",
    "FromAddress": "atendimento@poupatempodigital.com.br",
    "LocalDomain": "poupatempodigital.com.br",
    "MailServerAddress": "mail.poupatempodigital.com.br",
    "MailServerPort": "25", //SMTP Port:  25 "or" 8889:,
    "UserId": "atendimento@poupatempodigital.com.br",
    "UserPassword": "senha"
  },
  "Sendgrid": {
    "ApiKey": "apiso sendgrid"
  },
  "Twilio": {
    "accountSid": "sid",
    "authToken": "token",
    "whatsAppFrom": "whatsapp:xxxx" //número de saída Twilio
  },

  "reCaptcha": {
    "chaveDeSite": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "chaveSecreta": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
  }
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Controladores

Sempre temos nos controladores um get (que chama o formulário para mostrar na tela) e um post (que recebe os dados do formulário quando você dá um SUBMIT).

No método do get precisamos passar o valor da chave do site, em data-siteky=”@ViewBag.ChavedoSite”

Podemos por no view da página uma ViewBag com a Mensagem de retorno, se quisermos, tendo algo assim: @ViewBag.MensagemDeRetorno:

<div class="alert alert-success" role="alert">
  @ViewBag.MensagemDeRetorno
</div>

Só que quando chamarmos pelo get, essa mensagem estará vazia. Podemos preparar a view para mostrá-la somente quando houver retorno pelo controlador POST.

ajuste o arquivo cshtml de edicao e ou criacao
     <div class="g-recaptcha" data-siteky="@ViewBag.ChavedoSite">
            </div>
            @if (@ViewBag.MensagemDeRetorno != "")
            {
            <div class="alert alert-success" role="alert">
                @ViewBag.MensagemDeRetorno
            </div>
            }
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-primary" />
            </div>
        </form>

GET

No get temos de pegar um valor que está em appsettings.json. Vamos precisar, no início do contrlador, definir _configuration.

rotina get do controlador de criacao (por exemplo, vale o mesmo para edicao)
  // GET: TP_BancosCaixas/Create
        public IActionResult Create()
        {
            //precisamos pegar o valor que está em Appsettings.json
            ViewBag.ChavedoSite = _configuration.GetSection("reCaptcha").GetSection("chaveDeSite").Value;
            ViewBag.MensagemDeRetorno = "";
            return View();
        }

No topo do arquivo acrescente:

using Microsoft.Extensions.Configuration;

Acrescente na chamada do controlador:

  • private readonly IConfiguration _configuration;
  • public PessoasController(ApplicationDbContext context,
    IConfiguration configuration)
  • _configuration = configuration;

Ficando algo assim:

 

rotina de inicio do controlador com as definicoes _configuration, para pegar os dados em appsettings.json
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using PoupaTempoDigital.Data;
using PoupaTempoDigital.Models;

namespace PoupaTempoDigital.Controllers
{
    [Authorize(Roles = Papeis.AdminEndUser)]
    [Authorize(Roles = Papeis.ColaboradorEndUser)]
    [Authorize(Roles = Papeis.ClienteEndUser)]
    public class TP_BancosCaixasController : Controller
    {
        private readonly ApplicationDbContext _context;
        private readonly IConfiguration _configuration;

        public TP_BancosCaixasController(ApplicationDbContext context
            ,IConfiguration configuration)

        {
            _context = context;
            _configuration = configuration;

        }

POST

 O post é mais chatinho. Tem que pessar para a API do Google as variáveis response e secret.

Acrescentar no topo da página: 

using System.Net;

using System.Net.Http; (provalmente vai ter que instalar no seu projeto)

using Newtonsoft.Json;

 

rotina POST do controlador
        // POST: TP_BancosCaixas/Create

        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("IdBancoCaixa,IdUsuario,NomeBancoCaixa,NumeroBanco,NumeroConta,NumeroAgencia,SaldoInicial,DataSaldoInicial,TipoDeConta")] TP_BancosCaixas tP_BancosCaixas)
        {
            if (ModelState.IsValid)
            {
                HttpClient httpClient = new HttpClient();
                var response = Request.Form["g-recaptcha-response"]; // that's how you get it from the Request object
            
                string secretKey = _configuration.GetSection("reCaptcha").GetSection("chaveSecreta").Value;
                var cliente = new WebClient();
               // var res = httpClient.GetAsync($"https://www.google.com/recaptcha/api/siteverify?secret={secretKey}&response={response}").Result;
                var resultado = cliente.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secretKey, response));
                var obj = JObject.Parse(resultado);
                var status = (bool)obj.SelectToken("success");
                if (status)
                {
          					//acrescentar somente se o seu form voltar para a mesma página de entrada de dados
                    //ViewBag.MensagemDeRetorno = "Validação do Google reCaptcha feita com SUCESSO !!!";
                    _context.Add(tP_BancosCaixas);
                    await _context.SaveChangesAsync();
                    return RedirectToAction(nameof(Index));
                }
                else
                {
                    ViewBag.MensagemDeRetorno = "Validação do Google reCaptcha FALHOU !!!";
                    ViewBag.ChavedoSite = _configuration.GetSection("reCaptcha").GetSection("chaveDeSite").Value;
                    return View(tP_BancosCaixas);
                }



                }
            return View(tP_BancosCaixas);
        }
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.

Formatando datas nas views do CORE MCV

Formatando datas nas views do CORE MCV

Para  ver as datas formatadas em qualquer view que utilize um campo de data, basta alterar o model que essa view utilza. A partir daí todas as views que usarem o mesmo model ficarão com as datas ajustadas.

Deverá ser acrescentada a linha (no caso para formatar datas no formato brasileiro, dia, mês e ano, sem hora:

[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]

Cada campo data do modelo ficará assim:

using System;
using System.ComponentModel.DataAnnotations;

namespace Coaching.Models
{
    public partial class Avaliacoes
    {
        public int IdAvaliacao { get; set; }
        public int IdAvaliado { get; set; }
        public int IdAvaliador { get; set; }
        public string TipoDeAvaliador { get; set; }
        public int IdTipoquestionario { get; set; }
        public bool? FlagFim { get; set; }
        public string Comentario { get; set; }
        public bool? FlagComentario { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataFim { get; set; }
        public int? NumRespostas { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataAltera { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataConvite { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataLembrete { get; set; }
        public int? NumPerguntas { get; set; }
        [DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime? DataCadastro { get; set; }
    }
}

Em cada arquivo de modelo onde fizer isso, deverá sempre acrescentar no topo:

using System.ComponentModel.DataAnnotations;

Essas alterações valem para toda aplicação. Onder que que essas datas entrarem, nas views, eles estarão formatadas.

Outra forma de fazer, mais consistente

Ajuste geral da Cultura do seu projeto

No arquivo Startup.cs, coloque as seguintes instruções logo no começo do método public void ConfigureServices(IServiceCollection services). Isso aplica essas definições para todas páginas da sua aplicação.

arquivo startup.cs - em public void ConfigureServices(IServiceCollection services)
public void ConfigureServices(IServiceCollection services)
        {
            //copie a partit daqui
            var cultureInfo = new CultureInfo("pt-BR");
            cultureInfo.NumberFormat.CurrencySymbol = "R$";

            CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
            CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
           //copie até aqui

            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });

Ajuste nos modelos que tem data (date)

Em todos os modelos em que tiver data, formaste o campo data da seguinte forma:

 

em todos os campos de modelos que tem data (date)
 //colocar no controle yourModel.DataCadastro = DateTime.Now; se quiser pré instanciar já 
//no get
        [DataType(DataType.Date)]
        [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
        [Display(Name = "Data de Cadastramento")]
        public DateTime DataCadastro { get; set; }

Acerto das Views (todas que mostram a data)

Simplesmente substitua todas as linhas (em geral nos arquivos index.cshtml e details.cshtm) que tenham:

<td>
@Html.DisplayFor(modelItem => item.DataCadastro)
</td>

Substitua por:

<td>
@Html.TextBoxFor(modelItem => item.DataCadastro, “{0:dd-MM-yyyy}”)
</td>

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.

Diferença de datas (datediff) no Core MVC

Diferença de datas (datediff) no Core MVC

O cálculo de diferença de datas (datediff) no Core é muito simples (mas há muita confusão n Internet sobre o assunto.

Um exemplo dentro de uma View, que tira a diferença de datas entre o dia corrente e uma data advinda do modelo da própria view:

@{var hoje = DateTime.Today;
        var limite = Model.DataFim;
        var DiasFaltantes = (limite - hoje).Value.Days;

    }

A variável hoje é um inteiro, tendo valores negativos se hoje for maior que a data na variável limite (capturada no modelo da View como Model.DataFim.

Isso ajuda muito, dentro do view, a tomar decisões de mostrar layouts diferentes (texto e ou dados) para cada uma das situações. Exemplo:

@{var hoje = DateTime.Today;
        var limite = Model.DataFim;
        var DiasFaltantes = (limite - hoje).Value.Days;

    }
    <div class="col-sm-4">
        @*/////botão edita questionário*@

        @if (DiasFaltantes >= 0) //ainda pode preencher avaliação
        {

            @*preenche avaliação*@
        if (Model.FlagFim == true) // preencheu completamente a avaliação, só pode editar
        {
            <a href="@Url.Action("Action", "Controller", new { @idAvaliacao = Model.IdAvaliacao })" class="btn btn-xs btn-success">
                Edite a Avaliação  @*Preenche Avaliação*@
                <span class="glyphicon glyphicon-list" aria-hidden="true"></span>
            </a>
        }
        else
        {
            @*edita avaliação*@
            <a href="@Url.Action("Action", "Controller", new { @idAvaliacao = Model.IdAvaliacao })" class="btn btn-xs btn-success">
                Preencha/Edite a Avaliação  @*Preenche Avaliação*@
                <span class="glyphicon glyphicon-list" aria-hidden="true"></span>
            </a>
            }

        }
        else//nao pode mais preencher avaliação
        {
            <h4 class="alert-danger text-center">Avaliação encerrada</h4>
        }
        }
    </div>

 

 

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.

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