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
- {
- //Todo: Error handling process goes here
- }
- }
- 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.

John ClarkPosted Feb 7, 2022, 6:08 PM
Corrections on above example in ValidateReCaptchaAttribute: line 10 - response = Newtonsoft.Json.JsonConvert.DeserializeObject<ResponseToken>(reCaptchaResponse); and line 52 - public List<string> ErrorCodes { get; set; }
Donald ShawPosted Jun 26, 2020, 11:35 AM
Anthony's solution worked well for us -- after we got rid of a jQuery error.
Anthony SchrothPosted Jun 13, 2020, 9:18 PM
To make this solution work, please make sure there is no jQuery errors or conflicts. If there are jQuery conflicts, just embed the jQuery url before the Recaptcha 3 ASP.Net MVC Helpers.
Aram RezaiePosted Mar 24, 2020, 3:27 AM
Thank you, but the string reCaptchaToken = filterContext.HttpContext.Request.Form[GoogleReCaptchaVariables.InputName]; This line of code returns blank, how can i slove it?
anonPosted Mar 6, 2020, 4:33 PM
Thank you for sharing this post. ?? I see two issues in your code in the ReCaptchaVerify method though: The re-instantiation of the HttpClient for each request instead of reusing the same static instance, and the use of .Result to get the result of an async method (improper usage of sync over async). Both of these can cause all kinds of wacky issues like deadlocks and socket exhaustion.
Nishant JainPosted Oct 7, 2019, 12:16 AM
Please provide your Email Id to me so that i can send you screenshots of my code, where i got stucked.
Nishant JainPosted Oct 3, 2019, 10:53 PM
This is an MVC application. And how could you relate the response with namespaces ?? The problem is as i said below that that particular Recaptcha Input field is returning blank. Thats the issue. Can you please test your code OR provide me a working example ?
Nishant JainPosted Oct 3, 2019, 6:35 AM
Again and again its giving me "missing-input-response" . Please help me with this ASAP. string reCaptchaToken = filterContext.HttpContext.Request.Form[GoogleReCaptchaVariables.InputName]; This line of code returns blank, i guess this is the problem. Please help
Nishant JainPosted Oct 3, 2019, 6:30 AM
Below problems are now solved after doing some research. Also, added a handler for proxy which was creating issues in response. Now, the only thing i am facing issues with the Response string.
Nishant JainPosted Oct 1, 2019, 4:36 AM
The above code is not working for me... its not rendering the Recaptcha on cshtml page (showing ;; ). Also two of the code lines are also creating errors. (HTTPClient and Newtonsoft.Json) already added namespaces for them but still showing errors. Please reply ASAP.
Ian BugejaPosted Sep 9, 2019, 4:29 PM
All use the source and not the pasted code there is some code missing, small parts but important.
Manfred MühlbacherPosted Jul 6, 2019, 5:29 AM
Hello Athon, thank you for the code. I tried to implement it to my project, but get a build error at:response = Newtonsoft.Json.JsonConvert.DeserializeObject(reCaptchaResponse); Cannot implicitly convert type 'object' to ResponseToken. An explicit conversion exists (Are you missing a cast?)' I am not familiar with ResponseToken. Would you have a suggestion how to fix this error?