This solution will demonstrate a straightforward approach in developing HTML Helper that will render the needed resources to make Google ReCaptcha 3 work in ASP.NET MVC.
Upon completing this solution, we will be able to,
- add or reuse Google ReCaptcha 3 HTML Helpers on views and partial views
- validate form submission without human interaction
- let the custom attribute "ValidateReCaptcha" do the work for us
Before creating the ASP.NET MVC project, we should already have a Google ReCaptcha 3 tracking account in place. If you don't have one yet, you may sign-in at
https://www.google.com/recaptcha/admin.
The following steps will walk us through the solution.
- Create a new ASP.NET MVC Project in Visual Studio 2017 using .NET Framework 4.6 and use a descriptive name (e.g. ReCaptcha3ASPNetMVCHelper) for your project.
- Compile and build your project to ensure that there are no compile-time errors.
- Add two new keys in <appSettings> of Web.config file - “reCaptchaSiteKey” and “reCaptchaSecretKey”. Copy and paste the following codes.
- <appSettings>
- <add key="reCaptchaSiteKey" value="site_key" />
- <add key="reCaptchaSecretKey" value="secret_key" />
- </appSettings>
- In the “Models” folder, add a new class and name it as “ReCaptchaForm.cs”. Copy and paste the following lines of code into that class.
- namespace ASPNetMVCWithReCaptcha3.Models
- {
- public class ReCaptchaForm
- {
- public string Message { get; set; }
- }
- }
- Under project, create a new folder “Classes” and add a new class “ReCaptcha.cs”. Copy and paste the following lines of code.
- using System;
- using System.Web;
- using System.Collections.Generic;
- using System.Web.Mvc;
- using System.Net.Http;
- using System.Configuration;
-
- namespace ASPNetMVCWithReCaptcha3.Classes
- {
- }
- Inside the namespace "ASPNetMVCWithReCaptcha3.Classes", add the following lines of code.
- Add a static class that will store the variables needed to render ReCaptcha.
- public static class GoogleReCaptchaVariables
- {
- public static string ReCaptchaSiteKey = ConfigurationManager.AppSettings["reCaptchaSiteKey"]?.ToString() ?? string.Empty;
- public static string ReCaptchaSecretKey = ConfigurationManager.AppSettings["reCaptchaSecretKey"]?.ToString() ?? string.Empty;
- public static string InputName = "g-recaptcha-response";
- }
- A static helper class will be essential to render hidden input for response token.
- public static class ReCaptchaHelper
- {
- public static IHtmlString ReCaptchaHidden(this HtmlHelper helper)
- {
- var mvcHtmlString = new TagBuilder("input")
- {
- Attributes =
- {
- new KeyValuePair<string, string>("type", "hidden"),
- new KeyValuePair<string, string>("id", GoogleReCaptchaVariables.InputName),
- new KeyValuePair<string, string>("name", GoogleReCaptchaVariables.InputName)
- }
- };
- string renderedReCaptchaInput = mvcHtmlString.ToString(TagRenderMode.Normal);
- return MvcHtmlString.Create($"{renderedReCaptchaInput}");
- }
-
- public static IHtmlString ReCaptchaJS(this HtmlHelper helper, string useCase = "homepage")
- {
- string reCaptchaSiteKey = GoogleReCaptchaVariables.ReCaptchaSiteKey;
- string reCaptchaApiScript = "<script src='https://www.google.com/recaptcha/api.js?render=" + reCaptchaSiteKey + "'></script>;";
- string reCaptchaTokenResponseScript = "<script>$('form').submit(function(e) { e.preventDefault(); grecaptcha.ready(function() { grecaptcha.execute('" + reCaptchaSiteKey + "', {action: '" + useCase + "'}).then(function(token) { $('#" + GoogleReCaptchaVariables.InputName + "').val(token); $('form').unbind('submit').submit(); }); }); }); </script>;";
- return MvcHtmlString.Create($"{reCaptchaApiScript}{reCaptchaTokenResponseScript}");
- }
- }
- Another helper class to render “span” to server as a placeholder for failed ReCaptcha validation message.
- public static IHtmlString ReCaptchaValidationMessage(this HtmlHelper helper, string errorText = null)
- {
- var invalidReCaptchaObj = helper.ViewContext.Controller.TempData["InvalidCaptcha"];
- var invalidReCaptcha = invalidReCaptchaObj?.ToString();
- if (string.IsNullOrWhiteSpace(invalidReCaptcha)) return MvcHtmlString.Create("");
- var buttonTag = new TagBuilder("span")
- {
- Attributes = {
- new KeyValuePair<string, string>("class", "text-danger")
- },
- InnerHtml = errorText ?? invalidReCaptcha
- };
- return MvcHtmlString.Create(buttonTag.ToString(TagRenderMode.Normal));
- }
- The custom attribute “ValidateReCaptchaAttribute” will do most of the background process particularly the communication with the ReCaptcha 3 api and the validation logic. The internal model class “ResponseToken” will be used to store the response data for reference.
- public class ValidateReCaptchaAttribute : ActionFilterAttribute
- {
- public override void OnActionExecuting(ActionExecutingContext filterContext)
- {
- string reCaptchaToken = filterContext.HttpContext.Request.Form[GoogleReCaptchaVariables.InputName];
- string reCaptchaResponse = ReCaptchaVerify(reCaptchaToken);
- ResponseToken response = new ResponseToken();
- if (reCaptchaResponse != null)
- {
- response = Newtonsoft.Json.JsonConvert.DeserializeObject(reCaptchaResponse);
- }
- if (!response.Success)
- {
- AddErrorAndRedirectToGetAction(filterContext);
- }
- base.OnActionExecuting(filterContext);
- }
-
- public string ReCaptchaVerify(string responseToken)
- {
- const string apiAddress = "https://www.google.com/recaptcha/api/siteverify";
- string recaptchaSecretKey = GoogleReCaptchaVariables.ReCaptchaSecretKey;
- string urlToPost = $"{apiAddress}?secret={recaptchaSecretKey}&response={responseToken}";
- string responseString = null;
- using (var httpClient = new HttpClient())
- {
- try
- {
- responseString = httpClient.GetStringAsync(urlToPost).Result;
- }
- catch
- {
-
- }
- }
- return responseString;
- }
-
- private static void AddErrorAndRedirectToGetAction(ActionExecutingContext filterContext, string message = null)
- {
- filterContext.Controller.TempData["InvalidCaptcha"] = message ?? "Invalid Captcha! The form cannot be submitted.";
- filterContext.Result = new RedirectToRouteResult(filterContext.RouteData.Values);
- }
-
- internal class ResponseToken
- {
- public bool Success { get; set; }
- public float Score { get; set; }
- public string Action { get; set; }
- public DateTime Challenge_TS { get; set; }
- public string HostName { get; set; }
- public List ErrorCodes { get; set; }
- }
- }
- Create a postback action in the Controller to implement the [ValidateReCaptcha] attribute.
- [HttpPost]
- [ValidateAntiForgeryToken]
- [ValidateReCaptcha]
- public ActionResult Index(ReCaptchaForm form)
- {
- return View(form);
- }
- In the View, add the following lines to create the form with a message text area and Submit button.
- @model ASPNetMVCWithReCaptcha3.Models.ReCaptchaForm
- @using ASPNetMVCWithReCaptcha3.Classes;
- @{
- ViewBag.Title = "ReCaptcha Form";
- }
- @using (Html.BeginForm())
- {
- @Html.AntiForgeryToken()
- @Html.LabelFor(model => model.Message)
- @Html.TextAreaFor(model => model.Message, new { @class = "form-control" })
- @Html.ReCaptchaValidationMessage()
- @Html.ReCaptchaHidden()
- @Html.ReCaptchaJS()
- <button type="submit" class="btn btn-primary">Send Message</button>
- }
- Rebuild the project to ensure it is error-free.
- Don't forget to test the form. A successful ReCaptcha response will allow the form to submit. Otherwise, an error message will display below the message text area on the failed response.