I've used reCAPTCHA many times in my WebForms applications. Now I'd like to add it to an ASP.NET MVC application.
I found what appears to be some good code in RecaptchaControlMvc but, incredibly, I haven't been able to find a single paragraph or example on how to use this control.
I've posted in the Google reCAPTCHA group but it's dead.
Can anyone point me to an example that uses this control, a paragraph about how to use it, or suggest an alternative?
Note: I know there are similar questions on stackoverflow, but none that I've found discuss how to use this control.
Some code here
You add the attribute like this:
[CaptchaValidator]
[AcceptVerbs( HttpVerbs.Post )]
public ActionResult SubmitForm( Int32 id, bool captchaValid )
{
.. Do something here
}
You render the captcha in your view:
<%= Html.GenerateCaptcha() %>
which is something like this:
public static string GenerateCaptcha( this HtmlHelper helper )
{
var captchaControl = new Recaptcha.RecaptchaControl
{
ID = "recaptcha",
Theme = "blackglass",
PublicKey = -- Put Public Key Here --,
PrivateKey = -- Put Private Key Here --
};
var htmlWriter = new HtmlTextWriter( new StringWriter() );
captchaControl.RenderControl(htmlWriter);
return htmlWriter.InnerWriter.ToString();
}
I can provide you an easy alternative method to use google recaptcha.
Here you can find the complete reference about Google new reCAPTCHA using asp.net mvc
First all of you need to Sign up & Generate Google reCAPTCHA API.
Go to http://www.google.com/recaptcha then click on the top right corner Get reCAPTCHA button
Second, Write html code in your view. Here replace text "Your sitekey here"
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<div>
#using (Html.BeginForm("FormSubmit", "Home", FormMethod.Post))
{
<div class="g-recaptcha" data-sitekey="Your sitekey here"></div>
<input type="submit" value="Submit" />
}
</div>
<span style="display:inline-block; font-size:20px;margin:20px 0;padding:20px;border:1px solid #D3D3D3">
#ViewBag.Message
</span>
<script src='https://www.google.com/recaptcha/api.js' type="text/javascript"></script>
3rd and last, Write action code for validate google reCaptcha
[HttpPost]
public ActionResult FormSubmit()
{
//Validate Google recaptcha here
var response = Request["g-recaptcha-response"];
string secretKey = "Your secret here";
var client = new WebClient();
var result = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secretKey, response));
var obj = JObject.Parse(result);
var status = (bool)obj.SelectToken("success");
ViewBag.Message = status ? "Google reCaptcha validation success" : "Google reCaptcha validation failed";
//When you will post form for save data, you should check both the model validation and google recaptcha validation
//EX.
/* if (ModelState.IsValid && status)
{
}*/
//Here I am returning to Index page for demo perpose, you can use your view here
return View("Index");
}
This is How I do it with ASP.Net MVC and ReCaptcha 3.
In Visual Studio 2015 or 2017, create a new ASP.NET MVC project and set the target .NET Framework to 4.6.2.
Create a descriptive name for your ASP.NET MVC project (e.g. ASPNetMVCWithReCaptcha3).
Build your application and make sure it compiles and is error free.
In Web.Config, create two new keys under and copy/paste the site key and secret key from the text editor where you saved these references.
<appSettings>
<add key="reCaptchaSiteKey" value="site_key" />
<add key="reCaptchaSecretKey" value="secret_key" />
</appSettings>
In the "Models" folder, create a new class "ReCaptchaForm.cs" and copy/paste the following lines of code.
namespace ASPNetMVCWithReCaptcha3.Models
{
public class ReCaptchaForm
{
public string Message { get; set; }
}
}
Create a new "Classes" folder in the project.
In the "Classes" folder, create a new class "ReCaptcha.cs" and copy/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
{
}
Add the following lines inside the namespace
Add a class "GoogleReCaptchaVariables" that will store 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";
}
Create a helper class 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}");
}
}
Add another helper class that renders "span" tag to display error message on failed ReCaptcha verification.
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));
}
Next, create an attribute "ValidateReCaptchaAttribute" to process the API call. Add an internal class "ResponseToken" to store response data. Then, implement a validation logic to show error message on failed verification.
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; }
}
}
In the controller, create a postback action that implements the [ValidateReCaptcha] attribute.
[HttpPost]
[ValidateAntiForgeryToken]
[ValidateReCaptcha]
public ActionResult Index(ReCaptchaForm form)
{
return View(form);
}
On your controller's view, add the following lines to render the form, message input and submit bottom.
#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>
}
Build the project and make sure it is error free. As a result, the form should submit if ReCaptcha API returns a successful response. In contrast, the error message will show up on the bottom of message input if a failed response has been returned by the API.
Related
I am setting up an ASP.NET MVC website and I am having a problem looping through a session variable in the client. The session variable is a List that I want to be a list of sites that will exist in a dropdown list in the navbar on all pages. So, this will exist within the _Layout.cshtml. At the moment I am setting the session variables with the HomeController.
I used this stackoverflow post to get as far as I have done so far. However this post did not deal with displaying the list in the client:
The backend code is below.
Session Class:
namespace Customer_Application.Models
{
public class UserSiteList
{
public string? Site { get; set; }
public string? User { get; set; }
}
}
SesssionExtensions class:
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Text.Json;
namespace Customer_Application.DataLayer
{
public static class SessionExtensions
{
public static void Set<T>(this ISession session, string key, T value)
{
session.SetString(key, JsonSerializer.Serialize(value));
}
public static T Get<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default : JsonSerializer.Deserialize<T>(value);
}
}
}
HomeController:
public IActionResult Index(int id)
{
string sql = $"SELECT ID FROM [database].[dbo].[tblLogin] WHERE ID = {id}";
string username = _SharedFunctions.String_Required(sql);
contxt.HttpContext.Session.SetInt32("userID", id);
contxt.HttpContext.Session.SetString("username", username);
// Gets the site list for the user and adds to session
userSiteList = new List<UserSiteList>
{
new UserSiteList { Site = "siteA", User = "dave.smith" },
new UserSiteList { Site = "siteB", User = "dave.smith" }
};
contxt.HttpContext.Session.Set<List<UserSiteList>>("userSites", userSiteList);
var value = HttpContext.Session.Get<List<UserSiteList>>("userSites");
object Dashboard_inf = new Dashboard();
return View(Dashboard_inf);
}
The value of 'value' is as expected:
And when returned using the code 'return Json(value)', the follow is returned to the client:
So I try to use this in the client:
_Layout.cshtml:
#inject IHttpContextAccessor contxt;
#{
var userSites = contxt.HttpContext.Session.Get("userSites");
}
<!DOCTYPE html>
<html lang="en">
<head>
.
.
.
<ul class="navbar-nav flex-grow-1 pt-1">
<li class="nav_item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
Sites
</a>
<ul class="dropdown-menu">
#foreach (var item in userSites)
{
<li>#item</li>
}
</ul>
</li>
.
.
However this just produces a list of ascii character codes within the drop down list.
I think that this is something to do with how the 'userSites' variable is assigned in the _Layout.cshtml. I am pretty sure that '.Get' is incorrect, but do not know how to assign this otherwise.
Any help would be appreciated.
if you want to pass two piece of data you will have to create a ViewMOdel
public class ViewModel
{
public List<UserSiteList>> userSiteList {get; set;}
public Dashboard dashboard {get;set;}
}
now you have to fix your controller code
// comment this code. What is this for?
//contxt.HttpContext.Session.Set<List<UserSiteList>>("userSites", userSiteList);
//var value = HttpContext.Session.Get<List<UserSiteList>>("userSites");
//
var model = new ViewModel{
dashboard = new Dashboard();
userSiteList= userSiteList
}
return View(model);
and fix a view model, replace
#{
var userSites = contxt.HttpContext.Session.Get("userSites");
}
with
#model ViewModel
and fix this code,should be
#foreach (var item in Model.userSiteList )
{
<li>#item.Site</li>
<li>#item.User</li>
}
I have following class
Basic Class
public class Basic
{
public int ID { get; set; }
public string NAME { get; set; }
}
I have following method to fill the Value to above class
Get Authers Id and Name Method
public IEnumerable<Basic> GetAuthersIdName()
{
.....
}
So in Web API layer Controller Class I'm getting above details like below
// GET: api/Authers/all
[System.Web.Http.Route("api/Authers/all")]
public IEnumerable<Basic> GetAuthersIdName()
{
return db.GetAuthersIdName();
}
Then I can have following URL to fetch above details
http://localhost:13793/api/Authers/all
So In my MVC Layer , model folder I created a class to handle above details like below
public class LibraryClient
{
private string AUTHER_URL = "http://localhost:13793/api/Authers";
//DropDown
public IEnumerable<Basic> GetAuthersIdName()
{
try
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri(AUTHER_URL);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = client.GetAsync("Authers/all").Result;
if (response.IsSuccessStatusCode)
return response.Content.ReadAsAsync<IEnumerable<Basic>>().Result;
return null;
}
catch
{
return null;
}
}
}
Then I create Controller class to populate above details on front end like below
Controller Class
public class BooksController : Controller
{
// GET: Books/Create
public ActionResult Create()
{
LibraryClient lc = new LibraryClient();
ViewBag.listAuthers = lc.GetAuthersIdName();
return View("Create");
}
// POST: Books/Create
[HttpPost]
public ActionResult Create(Book book)
{
LibraryClient lc = new LibraryClient();
lc.CreateBook(book);
return RedirectToAction("BookswithAuthers", "BookWithAuther");
}
}
View File
<div class="form-group">
#Html.LabelFor(model => model.Auther_Id, "Auther_Id", htmlAttributes: new { #class = "." })
<div class="col-md-10">
#Html.DropDownList("NAME", (IEnumerable<SelectListItem>)ViewBag.listAuthers, "---Select---");
#Html.ValidationMessageFor(model => model.Auther_Id, "", new { #class = "." })
</div>
</div>
But since I'm using IEnumerable<Basic> to populate in business layer I cannot use that type in frontend.
I saw many answers do this task using jQuery and Web API , without that path how can I populate my drop down with #Html.DropDownList()
You assigning IEnumerable<Basic> to ViewBag.listAuthers and in the view attempting to cast it to IEnumerable<SelectListItem> which cannot be done and will fail (and result in an exception). You need to create the SelectList in the GET method using either
ViewBag.listAuthers = new SelectList(lc.GetAuthersIdName(), "ID", "Name")
or
ViewBag.listAuthers = lc.GetAuthersIdName().Select(x => new SelectListItem
{
Value = x.ID.ToString(),
Text = x.Name
});
and since you want to bind to the selected value to the Auther_Id property of Book, then your view needs to be
#Html.DropDownList("Auther_Id", (IEnumerable<SelectListItem>)ViewBag.listAuthers, "---Select---")
or better, use the strongly typed method
#Html.DropDownListFor(m => m.Auther_Id, (IEnumerable<SelectListItem>)ViewBag.listAuthers, "---Select---")
However, your editing data so you should be using a view model and that view model will contain a property for the SelectList (say public IEnumerable<SelectListItem> AuthorList { get; set; } and then the view will be
#Html.DropDownListFor(m => m.Auther_Id, Model.AuthorList, "---Select---")
Refer this question/answer for more detail on implementing the view model and the associated controller methods.
I am starting in asp.net Mvc and making test, i am doing a simple chat using PubNub api and i want finish it using only razor code only and one page only.
Model Chat.cs:
namespace SimpleChat.Models
{
public class Chat
{
public string NuevoMensaje { get; set; }
public string TextArea { get; set; }
}
}
View:
#model SimpleChat.Models.Chat
#using (Html.BeginForm("Index","Chat",FormMethod.Post))
{
#Html.LabelFor(model => model.NuevoMensaje, "Nuevo Mensaje")
#Html.TextBoxFor(model => model.NuevoMensaje)
<input type="submit" class="btn-default" value="Enviar" />
#Html.TextAreaFor(model => model.TextArea)
}
Controller:
static string variante = "";
public ActionResult Index()
{
pubnub.Subscribe<string>("Chat", subCallback, connecCallBack, errorCallback);
//Chat nuevochat = new Chat();
return View();
}
[HttpPost]
public ActionResult Index(Chat chat)
{
pubnub.Publish<string>("Chat", chat.NuevoMensaje, userCallback, puberror);
chat.NuevoMensaje = "";
chat.TextArea =variante;
return View("Index",chat);
}
private void subCallback(string obj)
{
string[] retorno = obj.Split(',','"');
variante += "Richard dice:" + retorno[0] + "\n";
}
When i press submit don't get the new data, why?
If you want to render the updated value of TextArea property of your view model, You should clear the model state dictionary.
Model state dictionary has the initial values of your form inputs. So when razor (re)render the same view, It gives priority to the content in model state dictionary than the view model object you passed to the view.
You can use the ModelState.Clear() method to clear the model state dictionary values before returning to the view.
chat.TextArea = variante;
ModelState.Clear();
return View("Index",chat);
Assuming your variante variable has the updated text.
I want to pass additional data from my error handler attribute to error view.
This is how I use my attribute:
[ErrorHandler (View = "Error", Title = "Some title", Text = "Some text")]
public ActionResult Delete(int id, string type, DBRepository repository){...}
This is how I pass data:
public class ErrorHandler : HandleErrorAttribute
{
public string Title { get; set; }
public string Text { get; set; }
/*Some other stuff*/
filterContext.Controller.TempData["CustomErrorTitle"] = Title;
filterContext.Controller.TempData["CustomErrorText"] = Text;
}
And this is my error view:
#model HandleErrorInfo
#{
ViewBag.Title = TempData["CustomErrorTitle"];
}
<h1 class="text-danger">#TempData["CustomErrorTitle"]</h1>
<h1 class="text-danger">#TempData["CustomErrorText"]</h1>
#if (Request.IsAuthenticated && User.IsInRole("Admin"))
{
<div>
Exception details: #Model.Exception
</div>
}
My code works, but i don't want to use TempData.
Is there other way to pass data without using TempData?
yes there's another way using json like this
in you controller
return Json(new
{
CustomErrorTitle= Title ,
CustomErrorText= Text
});
and in your aspx when you call the action write
error:function(data)
{
// you can call your error attribute like this
//data.CustomErrorTitle or data.CustomErrorText
//and do what ever you want
}
I have a situation I can't solve alone... I have this object:
public class Service
{
...
public Configuration Conf{get; set;}
...
}
public class Configuration
{
...
public List<Gateway> Gateways{get; set;}
...
}
Now I have a page to create a new service and I want to add runtime (client-side) a partial view.
I have a page that accept as model the Service class.. and a partial view that have the gateway as model..
Everything seems to work..
#model ObjectModel.Entities.Configurations.Service
...
#section scripts {
<script type="text/javascript">
function loadPartial(event) {
event.preventDefault();
event.stopPropagation();
var $div = $(event.target).closest(event.data.divContainer),
url = $(this).data('url'), model = event.data.model;
$.post(url, function (model) {
$div.prepend(model);
});
}
$('#link_add_gateway').live('click', { divContainer: "#new_gateway", model: #Html.Raw(Json.Encode(Model)) }, loadPartial);
</script>
}
...
<div id="new_gateway">
<a id="link_add_gateway" class="cursor_pointer"
data-url='#Url.Action("RenderGateway", "Configuration")'>Aggiungi gateway</a>
</div>
<input type="submit" value="Create" class="btn btn-default" />
And here the controller:
//EDIT: Now service is valorized here too..
public ActionResult RenderGateway(Service service)
{
Gateway g = new Gateway();
service.Configuration.Gateways.Add(g);
return PartialView("~/Views/_Partials/Gateway/Edit.cshtml", g);
}
[HttpPost]
public ActionResult Create(Service service)
{
//Still nothing
}
Here the problem:
Service has no gateway valorized.. I think is correct, but I don't know how to solve it! I would like to associate the model of the partial view to the model of the page.
How can I do?
thank you
UPDATE:
public class Configuration
{
[XmlElement(ElementName = "gateway")]
public GatewaysList Gateways { get; set; }
public Configuration()
{
this.Gateways = new GatewaysList();
}
}
[Serializable]
public class GatewaysList : List<Gateway>
{
public Gateway this[int gatewayId]
{
get
{
return this.Find(g => g.GatewayId == gatewayId);
}
}
}
I think you shouldn't use get to do this call because you have to send parameters
So try something like this
$().ajax({method: 'POST', data: #Html.Raw(Json.Encode(Model)), //other parameters})
and then change
public ActionResult RenderGateway(ObjectModel.Entities.Configurations.Service service)
{
return PartialView("~/Views/_Partials/Gateway/Edit.cshtml",service);
}
the key for your problem is to use #Html.Raw(Json.Encode(Model)) to reSend your Model over pages
update
this code came from my working project so I'm sure that works, try sending the parameter as a string and then deserialize it
public ActionResult RenderGateway(string service)
{
var jsSettings = new JsonSerializerSettings();
jsSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
var deserializedModel = JsonConvert.DeserializeObject<Service >(service, jsSettings);
//now deserializedModel is of type Service
return PartialView("~/Views/Shared/something.cshtml", deserializedModel);
}
update 2
I see from your GatewayList class that it's an indexer. They can't be serialized by xmlserializer
You can do something like this
public class GatewaysList : List<Gateway>
{
[XmlIgnore]
public Gateway this[int gatewayId]
{
get
{
return this.Find(g => g.GatewayId == gatewayId);
}
}
[XmlArray(ElementName="GatewaysList")]
[XmlArrayItem(ElementName="Gateway", Type=typeof(Gateway))]
public List<Gateway> GatewaysList
{
get
{
}
set
{
}
}
}
Solved...
"Just" 1 row missed in my code... In my PartialView:
#using (Html.BeginCollectionItem("Configuration.Gateways"))
Now everything works correctly..
Ah... I have forgot to say that I had to install the BeginCollectionItem plugin and add to web.config the following line:
<add namespace="HtmlHelpers.BeginCollectionItem" />
in system.web.webPages.razor -> pages -> namespaces