In my previous blog I explained how the latest version of reCAPTCHA can be implemented in a EPiServer MVC website. Now I want to explain the same functionality but then for a website build with webforms.
There are a couple events that can be handled for adding or changing functionality of a XForm. One of these is the ControlsCreated event. This event is fired when all controls are created for rendering the form. This event can be used for adding controls to the form. For implementing the reCAPTCHA there are three controls that needs to be added: a literal control that contains the HTML elements and a script reference for rendering the reCAPTCHA control, a custom validator for the reCAPTCHA validation and a validation summary control for displaying an error message.
An event handler can be added for the ControlsCreated event in an initialization module.
[InitializableModule]
[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class XFormInitialization : IInitializableModule
{
public void Initialize(InitializationEngine context)
{
XFormControl.ControlSetup += XForm_ControlSetup;
}
public void XForm_ControlSetup(object sender, EventArgs e)
{
XFormControl control = (XFormControl)sender;
control.ControlsCreated += XForm_ControlsCreated;
}
private void XForm_ControlsCreated(object sender, EventArgs e)
{
var control = (XFormControl)sender;
AddReCaptchaControl(control); // add recaptcha control
AddValidationSummaryControl(control);
}
In the code below I make sure that the reCAPTCHA and validation summary control is added to the Controls collection property of the XFormControl object.
private void AddReCaptchaControl(XFormControl formControl)
{
if (formControl.EditMode)
{
// Don't render the recaptcha control when in edit mode
return;
}
var recaptchaValidator = new CustomValidator();
recaptchaValidator.ID = "reCaptchaCustomValidator";
recaptchaValidator.ValidationGroup = formControl.ValidationGroup;
recaptchaValidator.ErrorMessage = "ReCaptcha invalid";
recaptchaValidator.ServerValidate += RecaptchaValidatorOnServerValidate;
recaptchaValidator.Display = ValidatorDisplay.None;
var reCaptchaLiteral = new LiteralControl(new ReCaptcha().Render());
var literal = new LiteralControl("</td></tr><tr><td valign=\"top\">");
foreach (object control in formControl.Controls)
{
if (control.GetType() == typeof(Submit)) // Add the captcha control before the submit control
{
var index = formControl.Controls.IndexOf((Control)control);
formControl.Controls.AddAt(index, reCaptchaLiteral); // add the captcha control
formControl.Controls.AddAt(index + 1, recaptchaValidator); // add the captcha validation control
formControl.Controls.AddAt(index + 2, literal); // add the captcha validation control
break;
}
}
}
I first check whether the form is rendered in edit mode, this means that the form is loaded in EPiServer. The reCAPTCHA control only needs to be loaded in the website so if above is true then just stop executing the method. Next I created a custom validator control for checking whether the user passes the reCAPTCHA validation. The HTML elements and the JS script is placed in a literal control. Both the custom validator and literal control is added to the Controls collection property of the XFormControl object. To make sure that the reCAPTCHA control is rendered above the submit button I loop through the Controls collection and insert the controls before the submit button.
The custom validator control uses the server validate method for checking whether the user passes the reCAPTCHA validation. The code below contains a work around to make sure that the method is only called once. I haven’t figured out yet why this method is called twice after the form has been submitted.
private void RecaptchaValidatorOnServerValidate(object source, ServerValidateEventArgs args)
{
// For some reason this event handler is executed twice, so this check is created
if (HttpContext.Current.Items["recaptchaValidated"] != null && (bool)HttpContext.Current.Items["recaptchaValidated"])
{
return;
}
var validator = (CustomValidator)source;
var xFormControl = validator.Parent as XFormControl;
if (xFormControl != null)
{
args.IsValid = new ReCaptcha().Validate();
HttpContext.Current.Items.Add("recaptchaValidated", true);
}
}
I’ve created the class ReCaptcha for doing the validation and for returning the HTML and script reference value that is used for rendering the reCAPTCHA control. The validation functionality for the reCAPTCHA control is the same as I explained in my previous blog post EPiServer XForms reCAPTCHA MVC implementation.
public class ReCaptcha
{
public bool Validate()
{
var result = HttpContext.Current.Request.Form["g-recaptcha-response"];
var url = string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}",
ConfigurationManager.AppSettings["ReCaptchaSiteSecret"],
result);
var request = HttpWebRequest.Create(url);
var objStream = request.GetResponse().GetResponseStream();
if (objStream != null)
{
var objReader = new StreamReader(objStream);
var googleResults = objReader.ReadToEnd();
var recaptchaResult = JsonConvert.DeserializeObject<ReCaptchaResponse>(googleResults);
return recaptchaResult.Success;
}
return false;
}
public string Render()
{
return "<script src="https://www.google.com/recaptcha/api.js" async defer></script>" +
"<div id='contact-recaptcha' class='g-recaptcha' data-sitekey='" + ConfigurationManager.AppSettings["ReCaptchaSiteKey"] + "'></div>";
}
}
Next the AddValidationSummaryControl method, as the name of the method reveals, it adds a validation summary control to the form.
private void AddValidationSummaryControl(XFormControl formControl)
{
if (formControl.EditMode)
{
// Don't render the recaptcha control when in edit mode
return;
}
var controls = formControl.Controls;
var validationSummaryLiteralControl = new LiteralControl("</td></tr><tr><td valign=\"top\">");
var validationSummaryControl = new ValidationSummary();
validationSummaryControl.ValidationGroup = formControl.ValidationGroup;
validationSummaryControl.HeaderText = "Invalid fields: ";
validationSummaryControl.DisplayMode = ValidationSummaryDisplayMode.SingleParagraph;
controls.AddAt(controls.Count - 3, validationSummaryLiteralControl);
controls.AddAt(controls.Count - 3, validationSummaryControl);
}
Last the form is rendered on the page (aspx).
<EPiServer:Property runat="server" PropertyName="Form"></EPiServer:Property>
The below image shows how the reCAPTCHA is rendered and how the validation message is displayed when the user doesn’t pass the reCAPTCHA validation.