0

In the POST endpoint for my site's Contact page, which sends an email to the site owner based on user input, I am returning a ViewResult of the same view, but with a newly initialized (empty) view model.

My goal for the client is, upon receiving the POST response, give the user the same page, but with all the form fields emptied. Rather than this taking place, however, the user ends up on the same page with all of the same form information still filled out. The email is sent successfully, and no errors are thrown.

Any ideas?

Here are my GET and POST endpoints:

[HttpGet]
public ViewResult contact()
{
    return View(new ContactUsViewModel());
}

[HttpPost]
public async Task<ViewResult> contact(ContactUsViewModel inputModel)
{
    try
    {
        if (ModelState.IsValid)
        {
            string body = 
                "<div style='font-family: Arial, Helvetica, sans-serif; font-size: 13px; color: #444444;'>" +
                "<p style='font-size: 17px;'>Email from <strong>{0}</strong> ({1})</p>" +
                "<p>Date: {2}</p>" +
                "<p>Phone: {3}</p>" +
                "<p>Message:</p><p style='margin-left: 24px;'>{4}</p>" +
                "</div>";
            string to = ConfigurationManager.AppSettings["ContactUsEmailAddress"];
            MailMessage message = new MailMessage();
            message.To.Add(new MailAddress(to));
            message.Subject = "Message from " + inputModel.Name;
            message.Body = String.Format(body, new string[]
                {
                    inputModel.Name, inputModel.Email, DateTime.Now.ToLongDateString(), inputModel.Phone, inputModel.UserMessage
                }
            );
            message.IsBodyHtml = true;

            using (var smtp = new SmtpClient())
            {
                await smtp.SendMailAsync(message);
                // the "true" parameter in the constructor just sets a "Message sent" 
                // confirmation message in the view model that is displayed on the view 
                // via Razor.
                return View(new ContactUsViewModel(true));
            }
        }
        else
        {
            return View(inputModel);
        }
    }
    catch (Exception ex)
    {
        string ourEmailAddress = ConfigurationManager.AppSettings["ContactUsEmailAddress"];
        inputModel.PublicErrorMessage = "There was a problem sending your message. Please send an email directly to " +
            "<a href='mailto:" + ourEmailAddress + "'>" + ourEmailAddress + "</a> so we can hear from you :)";
        inputModel.InternalErrorMessage = ex.Message;
        return View(inputModel);
    }
}

In case this is relevant, here is my ContactUsViewModel as well:

public class ContactUsViewModel : BaseViewModel
{
    public ContactUsViewModel() { }
    public ContactUsViewModel(bool messageSent)
    {
        this.MessageSentConfirmation = "Your message has been sent. We will get back to you shortly!";
    }

    [Required(ErrorMessage = "Please include your name.")]
    public string Name { get; set; }

    [Required(ErrorMessage = "Please enter a valid email address.")]
    [EmailAddress(ErrorMessage = "Please enter a valid email address.")]
    public string Email { get; set; }

    [Phone(ErrorMessage = "Please enter a valid phone number.")]
    public string Phone { get; set; }

    [Required(ErrorMessage = "Please enter a message.")]
    public string UserMessage { get; set; }

    public string MessageSentConfirmation { get; private set; }
}

EDIT: I know that the Post-Redirect-Get design pattern would technically circumvent this issue, but it doesn't really resolve the technical limitation of not being able to return the same view with an empty view model. For that reason, I do not consider implementing PRG the solution.

Jacob Stamm
  • 1,660
  • 1
  • 29
  • 53
  • Well you know the correct way of doing this is PRG pattern. But the reason you seeing the same values is because your URL hasn't changed and browser is most likely caching the data on its own. – Preet Singh Aug 03 '16 at 21:36
  • 3
    You should be following the PRG pattern because you want to return a **new** view (not the one you submitted), but you can always use `ModelState.Clear()` - the behavior is explained in the 2nd part of [this answer](http://stackoverflow.com/questions/26654862/textboxfor-displaying-initial-value-not-the-value-updated-from-code/26664111#26664111) –  Aug 03 '16 at 22:05
  • @StephenMuecke thanks for the response. Bear with me, as I'm a bit new to this... but why would I need `ModelState.Clear()` if, in my return statement, I'm initializing a brand new model (`return View(new ContactUsViewModel(true))`)? Also, I'm trying to avoid having a dedicated URL which people can access that just says "Message received". So I've made a new view called "message-received", and my return could look like this: `return View("message-received)`. Is that a good idea? It's not an actual redirect response, but it keeps the URL the same. Your feedback and help is much appreciated. – Jacob Stamm Aug 03 '16 at 22:46
  • 1
    Read the link in my previous comment :) - `HtmlHelper` methods use the values from `ModelState` if they exist (which they do because you have a `ContactUsViewModel` parameter in the POST method so the values you submitted are added to `ModelState`) –  Aug 03 '16 at 22:50
  • @StephenMuecke Ah, yes! I see now! Apologies, I only read the first half of that answer, should have read the whole thing. The `ModelState.Clear` right before my return statement works perfectly. Thanks for the fix. – Jacob Stamm Aug 03 '16 at 23:10

1 Answers1

1

This is @StephenMuecke 's solution from the comments section. Executing ModelState.Clear() in my controller method before my return statement solves the issue.

Jacob Stamm
  • 1,660
  • 1
  • 29
  • 53