65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET Core Razor Page 电子邮件表单,使用 SendGrid 和 reCaptcha

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2019 年 2 月 11 日

CPOL

3分钟阅读

viewsIcon

10215

.NET Core Razor Page 电子邮件表单,使用 SendGrid 和 reCaptcha

引言

我想在部署在 Azure 上的 .NET Core App 驱动的网站上放置一个“联系我们”电子邮件表单。我希望通过使用 reCaptcha 来保护它,以减少垃圾邮件,并且我还想使用 SendGrid 而不是我自己的 smtp 服务器。

准备 SendGrid

SendGrid 有一个免费套餐,允许您每天发送多达 100 封电子邮件,这对于我的小型网站来说已经足够了,而且我始终可以选择升级到他们的付费套餐之一或切换回我的 SMTP 服务器。只需访问 https://sendgrid.com 并创建一个帐户,然后您可以获得一个 SendGrid API 密钥,您可以使用它来验证您通过他们的 API 发送的电子邮件。

准备 Google reCaptcha

要设置 Google reCaptcha,您需要一个 Google 帐户。我设置了一个 v2 reCaptcha,将 localhost(这样我可以在我的开发 PC 上进行本地测试)、我的域名和我的应用程序域名 azurewebsites.net 添加到有效域列表中。 Google 将为您提供两个代码片段以包含在客户端,一个引用他们的 JavaScipt 库,另一个是 div 元素,用于包含您希望 reCaptcha 出现的位置。他们还将为您提供一个服务器端密钥,该密钥将包含在您对 reCaptcha 验证的调用中。

Razor 页面代码

需要注意的关键点是

  • 我创建了一个名为 ContactFormModel 的类,该类将用于将用户输入的电子邮件详细信息移动到服务器,以及将通过 public 绑定属性 Contact 帮助显示和验证电子邮件详细信息的属性。
  • 我正在注入 IConfiguration,以便我可以从 appsettings.json 中读取值。
  • 我正在注入 IHttpClientFactory,因此我无需过于担心我将创建的 http 客户端的生命周期或数量。
  • 我正在注入 ILogger,以便我可以记录一些调试消息以帮助我调试代码。
  • 我将调用 reCaptcha 终结点,并且我将从 appsettings.json 中获取密钥。
  • 我还会从 appsettings.json 中获取 SendGrid API 密钥。
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
using SendGrid;
using SendGrid.Helpers.Mail;

namespace MainWebsite.Pages
{
    public class ContactFormModel
    {
        [Required]
        [StringLength(50)]
        public string Name { get; set; }
        [Required]
        [StringLength(255)]
        [EmailAddress]
        public string Email { get; set; }
        [Required]
        [StringLength(1000)]
        public string Message { get; set; }
    }

    public class ContactModel : PageModel
    {
        [BindProperty]
        public ContactFormModel Contact { get; set; }

        private readonly IConfiguration _configuration;
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly ILogger<ContactModel> _logger;

        public ContactModel(IConfiguration configuration,
            IHttpClientFactory httpClientFactory,
            ILogger<ContactModel> logger)
        {
            _configuration = configuration;
            _httpClientFactory = httpClientFactory;
            _logger = logger;
        }

        private bool RecaptchaPassed(string recaptchaResponse)
        {
            _logger.LogDebug("Contact.RecaptchaPassed entered");

            var secret = 
                _configuration.GetSection("RecaptchaKey").Value;

            var endPoint = 
                _configuration.GetSection("RecaptchaEndPoint").Value;

            var googleCheckUrl = 
                $"{endPoint}?secret={secret}&response={recaptchaResponse}";

            _logger.LogDebug("Checking reCaptcha");
            var httpClient = _httpClientFactory.CreateClient();

            var response = httpClient.GetAsync(googleCheckUrl).Result;

            if (!response.IsSuccessStatusCode)
            {
                _logger.LogDebug($"reCaptcha bad response {response.StatusCode}");
                return false;
            }

            dynamic jsonData = 
                JObject.Parse(response.Content.ReadAsStringAsync().Result);

            _logger.LogDebug("reCaptcha returned successfully");

            return (jsonData.success == "true");
        }

        public async Task<IActionResult> OnPostAsync()
        {
            _logger.LogDebug("Contact.OnPostSync entered");

            if (!ModelState.IsValid)
            {
                _logger.LogDebug("Model state not valid");
                return Page();
            }

            var gRecaptchaResponse = Request.Form["g-recaptcha-response"];

            if (string.IsNullOrEmpty(gRecaptchaResponse) 
                || !RecaptchaPassed(gRecaptchaResponse))
            {
                _logger.LogDebug("Recaptcha empty or failed");
                ModelState.AddModelError(string.Empty, "You failed the CAPTCHA");
                return Page();
            }

            // Mail header
            var from = new EmailAddress(
                Contact.Email, Contact.Name);
            var to = new EmailAddress(
                _configuration.GetSection("ContactUsMailbox").Value);
            const string subject = "Website Contact Us Message";

            // Get SendGrid client ready
            var apiKey = _configuration.GetSection("SENDGRID_API_KEY").Value;

            var client = new SendGridClient(apiKey);

            var msg = MailHelper.CreateSingleEmail(from, to, subject,
                Contact.Message, WebUtility.HtmlEncode(Contact.Message));

            _logger.LogDebug("Sending email via SendGrid");
            var response = await client.SendEmailAsync(msg);

            if (response.StatusCode != HttpStatusCode.Accepted)
            {
                _logger.LogDebug($"Sendgrid problem {response.StatusCode}");
                throw new ExternalException("Error sending message");
            }

            // On success just go to index page
            // (could refactor later to go to a thank you page instead)
            _logger.LogDebug("Email sent via SendGrid");
            return RedirectToPage("Index");
        }
    }
}

Razor 页面标记

需要注意的关键点是

  • 顶部设置的模型是 ContactModel(不是 ContactFormModel,如果您发现这些名称令人困惑,请原谅)。
  • 我正在使用“@section Scripts”来包含客户端 Google reCaptcha JavaScript 库(将其放在 _Layout 中会增加每个页面的负载,而不仅仅是这个页面)。
  • reCaptcha 的 div 元素包含在提交按钮之前(您需要更改 data-sitekey 属性)。
@page
@model MainWebsite.Pages.ContactModel
@{
    ViewData["Title"] = "Contact Us";
}
@section Scripts
{
    <script src='https://www.google.com/recaptcha/api.js'></script>
}

<h1>Contact Us</h1>

<div class="row">
    <div class="col-md-6">
        <h3>Send message:</h3>
        <form method="post">

            <div asp-validation-summary="All"></div>

            <div class="form-group row">
                <label class="col-form-label col-md-3 text-md-right" 
                    asp-for="Contact.Name">Name:</label>
                <div class="col-md-9">
                    <input class="form-control" asp-for="Contact.Name" />
                    <span asp-validation-for="Contact.Name"></span>
                </div>
            </div>

            <div class="form-group row">
                <label class="col-form-label col-md-3 text-md-right"
                    asp-for="Contact.Email">Email:</label>
                <div class="col-md-9">
                    <input class="form-control" asp-for="Contact.Email" />
                    <span asp-validation-for="Contact.Email"></span>
                </div>
            </div>

            <div class="form-group row">
                <label class="col-form-label col-md-3 text-md-right"
                    asp-for="Contact.Message">Message:</label>
                <div class="col-md-9">
                    <textarea class="form-control" rows="5"
                        asp-for="Contact.Message"></textarea>
                    <span asp-validation-for="Contact.Message"></span>
                </div>
            </div>

            <div class="form-group row">
                <div class="offset-md-3 col-md-9">
                    <div class="g-recaptcha" 
                         data-sitekey="enter recaptcha client key here"></div>
                </div>
            </div>

            <div class="form-group row">
                <div class="offset-md-3 col-md-9">
                    <button type="submit" class="btn btn-primary">
                        <span class="fa fa-envelope"></span> Send
                    </button>
                </div>
            </div>
        </form>
    </div>
</div>

appsettings.json

这是 appsettings.json 的一个片段,我没有包含我的密钥,您将需要在相关位置插入您的密钥。

"SENDGRID_API_KEY": "enter your sendgrid api key here",
"ContactUsMailbox": "enter email address you want mail sent to here",
"RecaptchaKey": "enter your recaptcha key here",
"RecaptchaEndPoint": "https://www.google.com/recaptcha/api/siteverify"

Startup.cs

您需要在 startup.csConfigureServices 方法中包含以下行,以确保 IHttpClientFactory 可用于注入。

services.AddHttpClient();

Program.cs

如果您想使用 ILogger 记录调试消息,您将需要在此处对其进行配置。我完全由您决定。

© . All rights reserved.