case 4670 multiple changes by Claude to break out message sending from the individual forms and support the trial server request
This commit is contained in:
@@ -1,124 +1,98 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MailKit.Net.Smtp;
|
||||
using MailKit.Security;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeKit;
|
||||
|
||||
namespace contact.Pages
|
||||
{
|
||||
[BindProperties(SupportsGet = true)]
|
||||
public class ContactFormModel
|
||||
{
|
||||
public ContactFormModel()
|
||||
{
|
||||
DbId = "n/a v7";
|
||||
}
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public string Company { get; set; }
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public string DbId { get; set; }
|
||||
[Required, EmailAddress]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class ContactModel : PageModel
|
||||
{
|
||||
private const string ConfirmedUrl = "https://ayanova.com/confirmed.htm";
|
||||
|
||||
//2022-05-06 Company names containing any of these are dropped silently but still get the
|
||||
//success redirect, so the spammer gets no signal and has to pay someone to enter them by hand
|
||||
private static readonly string[] SpamBlocks = { "google", "jumboleadmagnet", "boostleadgeneration" };
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public ContactFormModel Contact { get; set; }
|
||||
|
||||
//Set when the send fails, so Contact.cshtml can tell the visitor instead of claiming success
|
||||
public bool SendFailed { get; set; }
|
||||
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<ContactModel> _logger;
|
||||
|
||||
public ContactModel(IConfiguration configuration, ILogger<ContactModel> logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
Message = "Your contact page.";
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return Page();
|
||||
}
|
||||
|
||||
//2025-11-01 reCAPTCHA removed (Google service change). Replacement deferred, see runbook Phase 6.
|
||||
|
||||
var company = Contact.Company.ToLowerInvariant();
|
||||
if (SpamBlocks.Any(s => company.Contains(s)))
|
||||
{
|
||||
return Redirect(ConfirmedUrl);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await SendMailAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Log the whole submission so it can be recovered from the journal, not lost
|
||||
_logger.LogError(ex,
|
||||
"Contact form send failed. Name: {Name} | Company: {Company} | Email: {Email} | DbId: {DbId} | Message: {Message}",
|
||||
Contact.Name, Contact.Company, Contact.Email, Contact.DbId, Contact.Message);
|
||||
SendFailed = true;
|
||||
return Page();
|
||||
}
|
||||
|
||||
return Redirect(ConfirmedUrl);
|
||||
}
|
||||
|
||||
private async Task SendMailAsync()
|
||||
{
|
||||
var smtp = _configuration.GetSection("Smtp");
|
||||
|
||||
//Email is in the body as well as Reply-To, so it survives even if Reply-To can't be set
|
||||
var body = $"Name:\r\n{Contact.Name}\r\n\r\nCompany:\r\n{Contact.Company}\r\n\r\nEmail:\r\n{Contact.Email}\r\n\r\nDatabase Id:\r\n{Contact.DbId}\r\n\r\nMessage:\r\n{Contact.Message}";
|
||||
|
||||
var message = new MimeMessage();
|
||||
//From is always the authenticated account; the visitor goes in Reply-To (no sender spoofing)
|
||||
message.From.Add(new MailboxAddress("AyaNova contact form", smtp["User"]));
|
||||
message.To.Add(MailboxAddress.Parse("support@ayanova.com"));
|
||||
if (MailboxAddress.TryParse(Contact.Email, out var replyTo))
|
||||
{
|
||||
replyTo.Name = Contact.Name;
|
||||
message.ReplyTo.Add(replyTo);
|
||||
}
|
||||
message.Subject = $"Question about AyaNova from {OneLine(Contact.Company)}";
|
||||
message.Body = new TextPart("plain") { Text = body };
|
||||
|
||||
using var client = new SmtpClient();
|
||||
await client.ConnectAsync(smtp["Host"], smtp.GetValue<int>("Port"), SecureSocketOptions.StartTls);
|
||||
await client.AuthenticateAsync(smtp["User"], smtp["Password"]);
|
||||
await client.SendAsync(message);
|
||||
await client.DisconnectAsync(true);
|
||||
}
|
||||
|
||||
private static string OneLine(string s) => s.Replace("\r", " ").Replace("\n", " ");
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using contact.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace contact.Pages
|
||||
{
|
||||
[BindProperties(SupportsGet = true)]
|
||||
public class ContactFormModel
|
||||
{
|
||||
public ContactFormModel()
|
||||
{
|
||||
DbId = "n/a v7";
|
||||
}
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
[Required]
|
||||
public string Company { get; set; }
|
||||
//Optional; AyaNova clients deep-link to this page with their database ID.
|
||||
//Binding attributes left exactly as they were so that link keeps working.
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public string DbId { get; set; }
|
||||
[Required, EmailAddress]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
public class ContactModel : PageModel
|
||||
{
|
||||
private const string ConfirmedUrl = "https://ayanova.com/confirmed.htm";
|
||||
|
||||
//2022-05-06 Company names containing any of these are dropped silently but still get the
|
||||
//success redirect, so the spammer gets no signal and has to pay someone to enter them by hand
|
||||
public static readonly string[] SpamBlocks = { "google", "jumboleadmagnet", "boostleadgeneration" };
|
||||
|
||||
[BindProperty(SupportsGet = true)]
|
||||
public ContactFormModel Contact { get; set; }
|
||||
|
||||
//Set when the send fails, so the page can tell the visitor instead of claiming success
|
||||
public bool SendFailed { get; set; }
|
||||
|
||||
private readonly MailSender _mail;
|
||||
private readonly ILogger<ContactModel> _logger;
|
||||
|
||||
public ContactModel(MailSender mail, ILogger<ContactModel> logger)
|
||||
{
|
||||
_mail = mail;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return Page();
|
||||
}
|
||||
|
||||
//2025-11-01 reCAPTCHA removed (Google service change). Replacement deferred, see runbook Phase 6.
|
||||
|
||||
if (IsBlocked(Contact.Company))
|
||||
{
|
||||
return Redirect(ConfirmedUrl);
|
||||
}
|
||||
|
||||
//Email is in the body as well as Reply-To, so it survives even if Reply-To can't be set
|
||||
var body = $"Name:\r\n{Contact.Name}\r\n\r\nCompany:\r\n{Contact.Company}\r\n\r\nEmail:\r\n{Contact.Email}\r\n\r\nDatabase Id:\r\n{Contact.DbId}\r\n\r\nMessage:\r\n{Contact.Message}";
|
||||
|
||||
try
|
||||
{
|
||||
await _mail.SendToSupportAsync($"Question about AyaNova from {Contact.Company}", body, Contact.Name, Contact.Email);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
//Log the whole submission so it can be recovered from the journal, not lost
|
||||
_logger.LogError(ex, "Contact form send failed. Submission:\r\n{Body}", body);
|
||||
SendFailed = true;
|
||||
return Page();
|
||||
}
|
||||
|
||||
return Redirect(ConfirmedUrl);
|
||||
}
|
||||
|
||||
public static bool IsBlocked(string company)
|
||||
{
|
||||
var c = company.ToLowerInvariant();
|
||||
return SpamBlocks.Any(s => c.Contains(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user