Creating the contact page

Tagged: website development

Whilst creating my site I wanted to have a simple contact form to allow visitors to get in touch with me without necessarily exposing my email address to all and sundry, fortunately there are a number of modules available for Orchard that provide this functionality, unfortunately none of them were quite the right fit for my needs. But rather than roll my own I decided to use one of the existing modules and customise it to my needs.

In the end I settled on the Contact Us module as it's pretty light weight and doesn't add an un-necassary clutter to the admin interface other than a simple configuration menu item. However, what I didn't like about it was the default fixed text in the views and the routes that it added to my site. I also found a minor problem in that if any HTML tags were included in the message it would result in a "potentially dangerous request.form value..." error, and whilst that's good in terms of security it's not so helpful for the end user who'll be left a bit bemused.

So, here's what I did to get around these problems:

The views

The default views page title and content isn't really suited to a personal site, having a page title of Contact Us doesn't make too much sense when there's only one of me! So opening up the views folder I changed the Index.cshtml file to:

@model EWSNoggin.ContactUs.ViewModels.ContactUsFormViewModel
@{
    Layout.Title = "Contact";
}
@using (Html.BeginFormAntiForgeryPost())
{
    <fieldset>
        <legend>Contact Form</legend>

	<p>
            @Html.LabelFor(m => m.Name)
            @Html.TextBoxFor(m => m.Name)
            @Html.ValidationMessageFor(m => m.Name)
        </p>
        <p>
            @Html.LabelFor(m => m.Email)
            @Html.TextBoxFor(m => m.Email)
            @Html.ValidationMessageFor(m => m.Email)
        </p>

        <p>
            @Html.LabelFor(m => m.Message)
            @Html.TextAreaFor(m => m.Message, new { Cols = 50, Rows = 8 })
            <br />@Html.ValidationMessageFor(m => m.Message)
        </p>
        <p>

            <input class="button primaryAction" type="submit" value="@T("Send")" />
        </p>
    </fieldset>
}

This makes the title singular and adds a legend to the actual email form, in the future I might re-work the form validation to use a validation summary so that it makes things look a bit neater. It was then on to the Sent.cshtml file which is displayed once the contact email has been sent:

@model EWSNoggin.ContactUs.ViewModels.ContactUsFormViewModel
@{
    Layout.Title = "Contact";
}

I've removed a lot of the bloat from here so that all that it does is set the page title, the message sent confirmation will still be displayed through the Messages zone.

The routes

By default the modules contains two routes /contact and /contactus, as mentioned above /contactus isn't actually required so this got removed thusly:

using System.Collections.Generic;
using System.Web.Mvc;
using System.Web.Routing;
using Orchard.Mvc.Routes;

namespace EWSNoggin.ContactUs
{
	public class Routes : IRouteProvider
	{
		public void GetRoutes(ICollection<RouteDescriptor> routes)
		{
			foreach (var routeDescriptor in GetRoutes())
				routes.Add(routeDescriptor);
		}

		public IEnumerable<RouteDescriptor> GetRoutes()
		{
			return new[] {
					 new RouteDescriptor {
							 Priority	= 10,
							 Route		= new Route(
												"Contact",
												new RouteValueDictionary(new {
														area		= "EWSNoggin.ContactUs",
														controller	= "Home",
														action		= "Index",
													}),
												null,
												new RouteValueDictionary(new {
														area		= "EWSNoggin.ContactUs",
													}),
												new MvcRouteHandler())
						},
				};
		}
	}
}

Dangerous form values

To get around the problem of posting HTML tags within the email message involved a two pronged attack, first of all I needed to update the appropriate View Model to allow html tags within the message field by adding the [AllowHtml] attribute, which in turn meant adding in a reference to the System.Web.Mvc namespace. Doing this meant I didn't have to disable validation globaly through the web.config file.

using System;
using System.Web.Mvc;
using System.Collections.Generic;
using Orchard.ContentManagement.Drivers;
using Orchard.Core.Common.ViewModels;
using Orchard.Core.Common.Models;
using System.ComponentModel.DataAnnotations;

namespace EWSNoggin.ContactUs.ViewModels
{
	public class ContactUsFormViewModel
	{
		[Required(ErrorMessage = "Please fill in your name.")]
		[StringLength(100, ErrorMessage="That name is too long.")]
		public String Name { get; set; }

		[Required(ErrorMessage = "Please fill in your email address.")]
		[StringLength(100, ErrorMessage = "That email is too long.")]
		[RegularExpression(@"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$", ErrorMessage = "Please check your email address.")]
		public String Email { get; set; }

		[Required(ErrorMessage = "Please fill in the message.")]
		[StringLength(1000, ErrorMessage = "Your message is too long.")]
		[AllowHtml]
		public String Message { get; set; }
	} 
}

This on its own is enough to prevent the error from occurring, however it does leave me open to all sorts of crap being sent to me, so I needed to strip HTML out of the posted message by updating the appropriate HomeController action to remove html tags from the posted message. This was done by adding a simple class that replaces the <> tags with their ascii counter parts.

using System;
using System.Linq;
using System.Net.Mail;
using System.Web.Mvc;

using Orchard;
using Orchard.Localization;
using Orchard.ContentManagement;
using Orchard.Security;
using Orchard.Settings;
using Orchard.UI.Notify;

// externally referenced module for spam protection
using Orchard.Comments.Models;
using Orchard.Comments.Services;

using EWSNoggin.ContactUs.Drivers;
using EWSNoggin.ContactUs.Models;
using EWSNoggin.ContactUs.Services;
using EWSNoggin.ContactUs.ViewModels;
using Orchard.Themes;
using Orchard.Messaging.Services;
using System.Collections.Generic;

namespace EWSNoggin.ContactUs.Controllers
{
	[Themed]
	public class HomeController : Controller
	{
		private readonly IContactUsService _contactUsService;
		private readonly ICommentValidator _commentValidator;
		private readonly IMembershipService _membershipService;
		private readonly IMessageManager _messageManager;

		public HomeController(IOrchardServices orchardServices, IContactUsService contactUsService, ICommentValidator commentValidator, IMessageManager messageManager, IMembershipService membershipService)
		{
			Services	 = orchardServices;
			_contactUsService = contactUsService;
			_commentValidator = commentValidator;
			_membershipService = membershipService;
			_messageManager = messageManager;
		}

		public IOrchardServices Services { get; set; }
		public virtual ISite CurrentSite { get; set; }
		public virtual IUser CurrentUser { get; set; }
		public Localizer T { get; set; }

		[HttpGet]
		public ActionResult Index()
		{
			//var page = _contactUsService.Get();
			//var model = Services.ContentManager.BuildDisplay(page);
			return View(new ContactUsFormViewModel());
		}

		[HttpPost]
		public ActionResult Index(ContactUsFormViewModel form)
		{
			if (!ModelState.IsValid)
			{
				return View(form);
			}


			if (validateContactMessage(form))
			{
				var settings = _contactUsService.Get();
				var recipient = _membershipService.GetUser(settings.RecipientUserName);

				if (recipient == null || String.IsNullOrEmpty(recipient.Email))
				{
					Services.Notifier.Warning(T("Site error: Couldn't send message. Site owner needs to set valid recipient user with an email address."));
					return View(form);
				}

				_messageManager.Send(recipient.ContentItem.Record, "ContactUs", "Email",
										new Dictionary<String, String> {
											{ "Name", form.Name },
											{ "Email", form.Email },
											{ "Message", FormatMessage(form.Message) }
										});

				Services.Notifier.Information(T("Thank you for your message."));
			}
			else
			{
				// Message stopped because it looked like spam
				Services.Notifier.Information(T("Sorry we could not send your message."));
			}

			return View("Sent", form);
		}

		private bool validateContactMessage(ContactUsFormViewModel form)
		{
			var comment = new CommentPart() { Record = new CommentPartRecord {
														Author		= form.Name,
														Email		= form.Email,
														CommentText	= form.Message,
														SiteName	= "",
													} };
			return _commentValidator.ValidateComment(comment);
		}
		
		private string FormatMessage(string message)
		{
			return message.Replace("<", "&lt;").Replace(">", "&gt;");
		}
	}
}

This method is by no means the greatest and in future I will be re-visiting this once I have come up with a better method of handling any HTML that may be posted through the contact form.

Add a Comment