Minimum Viable Product - Part 6 - Contact Form

Build a form and somebody will use it

So far in this series we've picked our blog features, we've setup document types, data types, put together some templates and learned about tagging articles. Today we will look at a basic feature of any website — the contact form.

For my blog I put a little twist on what happens after the user presses submit. Instead of sending the usual email, we will post a message to our custom Slack account. If you'd like to send an email instead, it's just a matter of changing a few lines of code (along with having an SMTP service).

Plotting a course

Our form will be very simple, it will just have a text field with an email address and a textarea for a message. Once submitted the following will occur:

  1. Validate the cross site forgery token
  2. Validate the model (email and message fields)
  3. Save the message in Umbraco
  4. Send a message to Slack

As usual in Umbraco there are countless ways to do form with Umbraco. You could use Umbraco Forms, Wufoo/FormStack or just plain old MVC. Selection of what you want to do is purely up to the requirements you will need for your site. If you need a user to create custom forms with full Umbraco integration, you might use Umbraco Forms. If you need similar functionality and are willing to write an API endpoint that integrates with Wufoo/FormStack, you can do that too. But for this simple form, we'll just stick to MVC.

Grocery shopping

Let's look at the different components we will need for our simple little form. We will need the following:

  • A model to represent the email/message information
  • A partial view to display the model above
  • A page to show the partial view
  • A controller to handle the submission
  • A Slack account or SMTP server
  • A document type to represent the message in Umbraco for saving the message

Cooking our dinner

The model that will represent the message from the user is completely non-Umbraco:

using System.ComponentModel.DataAnnotations;

namespace KGLLC.Umbraco.Models
{
    public class ContactFormSubmission
    {
        [Required]
        [EmailAddress]
        public string Email { get; set; }
        [Required]
        public string Message { get; set; }
    }
}

The model simply requires both pieces of data and further requires the 'email' field to be properly formatted as an email address.

Next we need a partial view that will represent the form itself. You'll see a mix of MVC/Umbraco code in the following view. If you're new to MVC you might want to freshen up on how forms work in that framework.

@using KGLLC.Umbraco.Controllers
@model KGLLC.Umbraco.Models.ContactFormSubmission

@* BeginUmbracoForm is where we tell our form where to POST it's data to *@
@using (Html.BeginUmbracoForm<FormController>("HandleSend", new object { }, new { @class = "email-form", @role = "form" }))
{
    @* anti-forgery token gets put on our form here *@
    @Html.AntiForgeryToken()

    <h2>Send a message to Kevin</h2>

    <div class="form-group">
        @Html.LabelFor(m => m.Email, "Your Email *")
        @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        @Html.ValidationMessageFor(m => m.Email)
    </div>

    <div class="form-group">
        @Html.LabelFor(m => m.Message, "Your Message *")
        @Html.TextAreaFor(m => m.Message, new { @class = "form-control" })
        @Html.ValidationMessageFor(m => m.Message)
    </div>

    <div class="overflow-hidden">
        <input class="btn btn-success pull-right" type="submit" value="Send" />
    </div>
}

@* this is just a little extra bit that handles showing a message if the model is invalid *@
@if (TempData["Status"] != null)
{
    <p class="field-validation-error text-right form-status">@TempData["Status"]</p>
}

As we see in the partial view above, when the user submits the form, the 'HandleSubmit' method of the 'FormController' handles the submission. The controller inherits from SurfaceController which is a normal MVC controller with Umbraco powers imbued on it:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using G42.SecureSettings.Services;
using G42.UmbracoGrease.G42NodeHelper;
using G42.UmbracoGrease.G42Slack.Helpers;
using G42.UmbracoGrease.G42Slack.Models;
using KGLLC.Umbraco.Models;
using Umbraco.Core.Logging;
using Umbraco.Web.Mvc;

namespace KGLLC.Umbraco.Controllers
{
    public class FormController : SurfaceController
    {
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult HandleSend(ContactFormSubmission model)
        {
            if (!ModelState.IsValid)
            {
                TempData["Status"] = "* Please enter the required fields.";

                //returns to the current page with the model filled out
                return CurrentUmbracoPage();
            }

            //using my own custom finder for locating the ContactFormPage
            var contactPage = NodeHelper.Instance.CurrentSite.Home.Children.First(x => x.DocumentTypeAlias == "ContactFormPage");

            //create a new submission
            var submission = Services.ContentService.
                CreateContent(
                    string.Format("{0} on {1}", model.Email, DateTime.UtcNow.ToString("o")), 
                    Services.ContentService.GetById(contactPage.Id), 
                    "ContactFormSubmission"
                );

            submission.SetValue("email", model.Email);
            submission.SetValue("message", model.Message);

            Services.ContentService.SaveAndPublishWithStatus(submission);

            //now let's post a message to Slack, we could replace all of this with some code to send an email instead
            var attachments = new List<SlackAttachmentModel>();

            attachments.Add(new SlackAttachmentModel()
            {
                Fields = new List<SlackAttachmentFieldModel>() {               
                    new SlackAttachmentFieldModel()
                    {
                        Title = "Email From",
                        Value = model.Email
                    },
                    new SlackAttachmentFieldModel()
                    {
                        Title = "Message",
                        Value = model.Message
                    }
                }
            });

            SlackHelper.SaySomething("A user filled out the contact form:",
                "ContactFormBot",
                "http://you_slack_webhookurl_that_slack_gives_you",
                "@a_user_name",
                ":mailbox_with_mail:", 
                attachments);

            //success!
            //our view will display a thank you message
            TempData["Status"] = "Success";

            return RedirectToCurrentUmbracoPage();
        }
    }
}

The controller handles all of the logic after the submission. To keep track of what functionality belongs to what library, review the following:

  • The SurfaceController inheritance gives us easy access to Umbraco helpers like 'CurrentUmbracoPage' which persists the model after an invalid submission (use RedirectToCurrentUmbracoPage when you want the model to be empty after a successful submission)
  • We're creating a new ContactFormSubmission under the ContactPage using the Umbraco Services
  • We're using a custom library to send a message to Slack, you can use your own or decide to send an email instead
  • The rest is just regular old MVC

After a successful submission, your Slack client should have a new message as well as your Umbraco contact form page should have a new child under it. As mentioned in part 3, if you foresee having many submissions, you may want to store the submissions in a custom table instead of Umbraco directly.

Here's a idea of what it should look like for each screen:

You will also need to configure a document type for the ContactFormSubmission like this so that submissions get stored in Umbraco properly. Finally we need to see the ContactFormPage view to tie it all together:

@using KGLLC.Umbraco.Models
@inherits Umbraco.Web.Mvc.UmbracoTemplatePage
@{
    Layout = "Base.cshtml";

    //if our controller set this to 'Success', show the thank you message.
    var isSubmission = TempData["Status"] != null && TempData["Status"].ToString() == "Success";
}
@section FooterScripts {
    <script src="/assets/scripts/jquery.parallax.js"></script>
}

@Html.AntiForgeryToken()

<section id="main">

    @if (isSubmission)
    {
        //clears out the word 'Success' from showing up below the form
        TempData["Status"] = null;

        <div class="break secondary-break no-top-margin">
            <h3>Thank you for contacting me!</h3>
            <p>
                I will get back to you as soon as possible. Remember if you are a client you can also reach me directly on <a href="/slack">Slack</a>.
            </p>
            <p>
                I'm based in the Eastern Time (ET) zone in the United States, it may take some time to respond if you are on the other side of the marble.
            </p>
        </div>
    }
    else
    {
        <div class="blog">
            <section class="parallaxModule title" data-parallax="scroll" data-image-src="/assets/images/sketch.jpg?width=1200">
                <div class="container">
                    <h1>Contact Me</h1>
                </div>
            </section>
        </div>
    }

    <div class="container">
        <div class="row">
            <div class="col-md-8 form-container overflow-hidden">
                @* Show our form and start with a blank model, if the model fails Umbraco will persist our partially filled out model *@
                @Html.Partial("~/Views/Partials/ContactForm.cshtml", new ContactFormSubmission())
            </div>
            <div class="col-md-4">
                <h3>Contact me!</h3>
                <p>
                    You can simply fill out the form provided or use one of the methods listed in the footer of this page. Clients can contact me directly via <a href="/slack">Slack</a>.
                </p>
                <p>
                    I'm based in the Eastern Time (ET) zone in the United States, it may take some time to respond if you are on the other side of the marble.
                </p>
            </div>
        </div>
    </div>
</section>

Summary

Ok, hopefully that didn't feel like moving the Earth and the heavens to get a simple contact form up and running. If you need editors to directly change form inputs, you'll need to use Umbraco Forms or another solution. If you go with your own MVC-like forms in Umbraco you can decided where to store the submissions and/or how to send notifications to interested parties. Next up we will handle the amazing built-in search functionality of Umbraco.