I am implementing Search by siteId feature using typescript and C#. I am not able to connect to C# controller from frontend request.
Here i am suppose to pass siteId as parameter. Please find my code below.
HTML:
<div class="form-group">
<label>Site Id search:</label>
<input type="text" title="You can search for a partial SiteId or a list of SiteId separated by semicolons" ng-model="vm.siteIdFilter" /> <button ng-click="vm.loadAvailableDevices()"><i class="fa fa-search" aria-hidden="true"></i> Search</button><br />
</div>
controller.ts
loadAvailableDevices(currentPage: number = 1) {
if (currentPage === 1) {
this.isLoading = true;
this.availableDevices = new Array<any>();
}
let queryFilter: any = { serialNumber: this.serialNumberFilter, deviceType: this.selectedDeviceType };
this.deviceService.loadUpdatableDevices(this.currentTenant.Id, queryFilter, this.siteIdFilter)
.then((response: any) => {
this.availableDevices = this.availableDevices.concat(response);
this.deviceCnt = this.availableDevices.length;
this.isLoading = false;
});
}
service.ts
loadUpdatableDevices(tenantId: number, filter: any, siteId: string): ng.IPromise<any> {
const uri = this.routes.getUpdatableDevices.replace('{:tenantId}', tenantId.toString());
return this.restService
.get(uri, { siteId, filter }, true)
.then((response: any) => {
if (response.data) {
return response.data;
}
})
.catch((response: any) => {
return this.$q.reject(response);
});
}
RoutePath
getUpdatableDevices: '/device-management/tenants/{:tenantId}/updatableDevices'
C# Controller.cs
[Route("tenants/{tenantId}/updatableDevices")]
[AcceptVerbs("GET")]
public IEnumerable<EtpDevice> GetUpdatableDevices(int tenantId, [FromUri] string filter, string siteId)
{
var connectedUser = GetUser();
if (siteId != null)
{
var siteList = this.DataAccess.GetSitesListById(connectedUser, siteId);
}
}
I am not able to connect to C# controller when I pass siteid from the frontend. Below is the error i am getting in inspect element.
"{"StatusCode":500,"Message":"Can't bind multiple parameters ('siteId') to the request's content."}"
May I know what's wrong with the code? Please help me to fix this. Thanks in advance.
Try to modify your service.ts as follows, pass the siteId parameter as a query string instead of the request body.
loadUpdatableDevices(tenantId: number, filter: any, siteId: string): Promise<any> {
const uri = this.routes.getUpdatableDevices.replace('{:tenantId}', tenantId.toString());
return this.restService
.get(uri + `?filter=${encodeURIComponent(JSON.stringify(filter))}&siteId=${siteId}`)
.then((response: any) => {
if (response.data) {
return response.data;
}
})
.catch((response: any) => {
return Promise.reject(response);
});
}
Assume that your AngularJS side successfully passes the nested object as params to the API.
The Uri should be as:
/device-management/tenants/{:tenantId}/updatableDevices/?filter.serialNumber=<SerialNumberValue>&filter.deviceType=<DeviceTypeValue>&siteId=<SiteIdValue>
Create the DeviceFilterModel class to match the object type that receives from Uri.
public class DeviceFilterModel
{
public Filter Filter { get; set; }
public string SiteId { get; set; }
}
public class Filter
{
public string SerialNumber { get; set; }
public string DeviceType { get; set; }
}
Modify the GetUpdatableDevices API action method signature as below:
[Route("tenants/{tenantId}/updatableDevices")]
[AcceptVerbs("GET")]
public IEnumerable<EtpDevice> GetUpdatableDevices(int tenantId,
[FromUri] DeviceFilterModel model)
Reference: Using [FromUri]
Related
const [payload, setPayload] = useState({
Email: null,
Password: null
});
const handleSubmit = async (e) => {
e.preventDefault();
var json = JSON.stringify(payload);
try {
const { data } = await axios.post(
"https://localhost:5001/api/User/Login",
{
json
}
);
console.log(data);
ctxDispatch({ type: "USER_LOGIN", payload: data });
localStorage.setItem("userInfo", JSON.stringify(data));
toast.success("Login Successful");
navigate("/");
} catch (err) {
toast.error(getError(err));
}
};
[AllowAnonymous]
[HttpPost("Login")]
public async Task<ActionResult<UserDTO>> Login([FromBody] LoginDTO loginDTO)
{
//LoginDTO loginDTO = Newtonsoft.Json.JsonConvert.DeserializeObject<LoginDTO>(input);
var pwd = Encrypt.ConvertToEncrypt(loginDTO.Password);
User currentUser = await _context.user.FirstOrDefaultAsync(user =>
user.Email.ToLower() == loginDTO.Email.ToLower()
&&`enter code here`
user.Password == pwd);
}
public class LoginDTO
{
public string Email { get; set; }
public string Password { get; set; }
}
When I post this string to my backend it attempts to parse my string but fails at the first char {. When i send this string wrapped in quotes through swagger it passes with no issues. When i send this string through postman with no quotes wrapped around the string I get the same exact error as i get when posting through my frontend.
fix the action input parameters, you don't need to deserialize it , it will be deserialized by MVC
public async Task< ....([FromBody] LoginDTO loginDto)
and since you are sending a json string, add content type to axios
axios.post('..your url', json, {
headers: {
// Overwrite Axios's automatically set Content-Type
'Content-Type': 'application/json'
}
});
I have function in a C# controller file that makes an API call to fetch some placeholder data that is hard coded locally.
[Route("api/ReportingCenter/GetReportList")]
[HttpGet]
public class ReportController : ApiController
{
public async Task<IHttpActionResult> GetReportList(CancellationToken cancellationToken)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken,
HttpContext.Current.Response.ClientDisconnectedToken))
{
var userId = 11;
var projId = 22;
using (var reportDb = new ReportDb(userId, projId))
{
var list = reportDb.GetReports(userId, projId);
return Json(list);
}
}
}
}
public class ReportDb : PortalDb
{
public List<ReportInfo> GetReports(int userid, int projectid)
{
var ReportList = new List<ReportInfo>();
ReportList.Add(new ReportInfo(1,"file1.pdf", 11, "a file with data in it", 11112222,"pdf", 1, "22"));
}
}
I coded a button and a function with an aspx file that should make an axios call to run the GetReportList function from the controller. When the button is click, nothing happens. Any suggestions on how to make it so that the API call is made when the button is clicked in the aspx file?
<asp:Content ID="Content5" ContentPlaceHolderID="primaryContent" runat="server">
<div id="reporting-app-container">
<input type="button" value="ReportInfo" class="btn btn-sm btn primary"
onclick="getReportInfo"/>
</div>
<script src="/ReportsModule/app/dist/reporting-app-bundle.js"></script>
<script type="text/javascript">
$(document).ready(function () {
window.ReportingApp();
});
async function getReportInfo(id) {
console.log("pre-axios call")
const response = await axios.get("api/ReportingCenter/GetReportList")
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
});
}
</script>
</asp:Content>
.then and .catch are methods of a Promise, which is what you'd get if you called axios.get without awaiting it. A Promise represents an action which you've started in the background, which may or may not have finished yet, and which can call callback functions supplied by you if/when it completes successfully or throws an error. Or at least that's a very short description of what it is.
To pass your callback functions to the Promise returned by axios.get, remove the async and await, a bit like this:
function getReportInfo(id) {
axios.get('api/ReportingCenter/GetReportList')
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
.then(function () {
// always executed
});
}
If you want response to be the actual response from the API, rather than a Promise to give you the actual response once the web request has completed, then you can await the Promise, a bit like this:
async function getReportInfo(id) {
try {
const response = await axios.get('api/ReportingCenter/GetReportList');
console.log(response);
} catch (error) {
console.error(error);
}
}
A note of caution if you choose the second option - are you sure all your users' browsers support ECMAScript 2017? If not then the await / async approach probably isn't a good idea and you should stick with the first option.
Code snippets are adapted from this bit of the axios API documentation
Moved the javascript function that handles the axios call into reporting-app.js. Vue is being used on top of javascript. The .aspx file and the Vue app are connected by the "el" key, with the value set to the html id: '#reporting-app-container".
.aspx file
<div>HELLO WORLD!</div>
<div id="reporting-app-container">
<input type="button" value="ReportInfo" class="btn btn-sm btn primary" #click="getReportInfo" />
</div>
<script src="/ReportsModule/app/dist/reporting-app-bundle.js"></script>
.js file
import Vue from 'vue';
window.ReportingApp = function () {
new Vue({
el: '#reporting-app-container',
//store: FilesStore,
data: {},
components: {
'reporting-component': reporting_component,
'list-reports-component': list_report_component
},
methods: {
getReportInfo: function (id) {
console.log("pre-axios call");
axios.get("/api/ReportingCenter/GetReportList")
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
});
}
}
});
}
The javascript and the C# Controller file are linked by the axios method within the methods section of the Vue app.
Controller.cs
using ###.###.###.api.Models;
namespace ###.###.ReportsModule.api
{
public class ReportController : ApiController
{
protected readonly IPortalStateService PortalState;
public ReportController(IPortalStateService portalStateService)
{
PortalState = portalStateService;
}
[Route("api/ReportingCenter/GetReportList")]
[HttpGet]
public IHttpActionResult GetReportList(CancellationToken cancellationToken)
{
using (var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken,
HttpContext.Current.Response.ClientDisconnectedToken))
{
var user = PortalState.GetUserId();
if (user == null)
{
return BadRequest("User is not allowed");
}
var userId = 11;
var projId = 22;
try
{
using (var reportDb = new ReportDb(userId, projId))
{
var list = reportDb.GetReports(userId, projId);
return Json(list);
}
}
catch (Exception e)
{
return Json(e);
}
}
}
}
public class ReportDb : PortalDb
{
public List<ReportInfo> GetReports(int userid, int projectid)
{
var ReportList = new List<ReportInfo>();
ReportList.Add(new ReportInfo(1, "file1.pdf", 11, "a file with data in it",
11112222, "pdf", 1, "22"));
return ReportList;
}
}
}
GetReports function uses the model from ReportInfo.cs that defines the field values and the constructor for the data.
ReportInfo.cs Model
namespace ###.###.reportsmodule.api.Models
{
public class ReportInfo
{
public long Id { get; set; }
public string Name { get; set; }
public int Ordinal { get; set; }
public string Description { get; set; }
public int ProjectId { get; set; }
public string SourceType { get; set; }
public int MinPermissionLevel { get; set; }
public string SourceId { get; set; }
public ReportInfo(long id, string name, int ordinal, string description,
int projectId, string sourceType, int minPermissionLevel, string sourceId)
{
Id = id;
Name = name;
Ordinal = ordinal;
Description = description;
ProjectId = projectId;
SourceType = sourceType;
MinPermissionLevel = minPermissionLevel;
SourceId = sourceId;
}
}
}
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
I have a action method with this signature:
public List<Member> Get([FromUri]MemberSearchModel searchModel)
The model looks like this:
public class MemberSearchModel
{
public string SearchBy { get; set; }
public string SearchValue { get; set; }
}
and im calling the api like this:
dataFactory.getMembers = function () {
var memberSearchModel = {
SearchBy: 'Name',
SearchValue: 'jaredites'
};
return $http.get(urlBase, memberSearchModel);
};
the method is hit but the model comes through with null values
Ive tried not using [FromUri] but then the model itself is null
Ive tried [FromBody] and the model comes through as null as well.
Is there something Im missing here?
It works when I call it specifying the params in the url
Please try this that :
dataFactory.getMembers = function () {
var memberSearchModel = {
SearchBy: 'Name',
SearchValue: 'jaredites'
};
return $http.get(urlBase, {params: memberSearchModel});
};
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.