How to catch g-recaptcha-response in ASP.NET ApiController? - c#

I am trying to post the form with ReCaptcha to ApiController.
<form>
<label for="name">Name:</label>
<input id="name" name="name" ><br/>
<label for="email">Email:</label>
<input id="email" name="email" type="email" ><br/>
<div class="g-recaptcha" data-sitekey="some_site_key"></div>
<input type="submit" value="Submit"/>
</form>
<script src="https://www.google.com/recaptcha/api.js"></script>
<script>
$(function () {
$('form').submit(function (event) {
var data = $('form').serialize();
console.log('form data: ', data);
$.post("api/test", data,
function (data) {
alert('success');
});
event.preventDefault();
});
});
</script>
Form the console.log() statement, I can see the values posted are under names of name, email and g-recaptcha-response.
I thought I could have a view model at the ApiController, like this:
public IHttpActionResult Post(FormModel model)
{
// automatic model binding to get the posted data.
}
public class FormModel
{
public string Name { get; set; }
public string Email { get; set; }
public string GRecaptchaResponse { get; set; }
}
But obviously there is no rule to bind g-recaptcha-response to GRecaptchaResponse. And a valid property name should not contain a dash -.
So the question is, how can we receive the g-recaptcha-response value at the server (ApiController) end?

Here is what I've found:
In ApiController:
public async Task Post()
{
var obj = await Request.Content.ReadAsAsync<JObject>();
var model = obj.ToObject<FormModel>();
//...
}
For the FormModel, use [JsonProperty]:
public class FormModel
{
public string Name { get; set; }
public string Email { get; set; }
[JsonProperty("g-recaptcha-response")]
public string GRecaptchaResponse { get; set; }
}

Related

Form error message not displaying (ASP.NET CORE)

I've tried and played around a lot, but I can't seem to get my form authentication working.
Before I add functionality to actually create a review, I need to get this working.
The site simply submits the form, regardless of what is in the review text element. It should be catching and displaying an error message if it is blank.
Model:
public class CreatedReview
{
public int Id { get; set; }
[StringLength(60, MinimumLength = 3)]
[Required]
public string Review1 { get; set; }
public int? RestaurantId { get; set; }
public int? UserId { get; set; }
[Required]
public int? Stars { get; set; }
public CreatedReview(string review, int stars)
{
this.Review1 = review;
this.Stars = stars;
}
}
Index.cshtml
#model CreatedReview
#{
}
<h1>Leave a review!</h1>
<form method="post" asp-action="LeaveReview">
<p>Restaurant</p>
<input type="text" name="name" />
<p>Zipcode</p>
<input type="number" name="zipcode" />
<p>Review</p>
<input type="text" name="review" />
<span asp-validation-for="Review1" class="text-danger"></span>
<p>Star Rating</p>
<input type="number" name="stars" />
<input type="submit" />
</form>
Controller:
public IActionResult LeaveReview(string name, int zipcode, string review, int stars)
{
if (!ModelState.IsValid)
{
return View("Index");
}
// create review blah blah blah (need to accept all of the parameters later)
var newReview = new CreatedReview(review, stars);
return View("Index");
}

How to convert a $('#form').serializeArray() to c# class object with Ajax?

I have a model class named Location :
public class Location
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
public int? Floor { get; set; }
public string ImgURL { get; set; }
public string Type { get; set; }
public double Longitude { get; set; }
public double Latitude { get; set; }
}
In my .cshtml.cs Razor page, I have a PageModel named MapLocationModel with a property named CurrentLocation :
[BindProperty]
public Location CurrentLocation { get; set; }
I am using this property within a modal form :
<form id="locationForm" method="post">
<!--CurrentLocation.Name-->
<div class="form-group col-md-9">
<label asp-for="CurrentLocation.Name" class="control-label"></label>
<input asp-for="CurrentLocation.Name" class="form-control" />
<span asp-validation-for="CurrentLocation.Name" class="text-danger"></span>
</div>
// Here all other properties
</form>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<input type="button" id="buttonSubmit" value="Submit" class="btn btn-primary" />
</div>
Now what I want to achieve is to pass CurrentLocation to an ajax post.
So far, I have written my ajax function :
var button = $("#buttonSubmit");
button.on('click', function () {
var location = $('#locationForm').serializeArray();
$.ajax({
type: "POST",
url: "/Map/MapLocation?handler=AsyncMyLocationAjax",
headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() },
data: { currentLocation: location },
success: function (response) {
alert(response);
}
});
});
But my handler always return null :
public ActionResult OnPostAsyncMyLocationAjax(Location currentLocation)
{
Console.WriteLine(currentLocation);
// currentLocation is always null
}
I think the problem is that the RequestVerificationToken is part of the serialization :
I have read a lot of posts on how to pass data with ajax but I still don't understand how to do it.
What am I doing wrong ? How I can convert var location = $('#locationForm').serializeArray(); in a Location object ?
Both serializeArray and serialize will generate __RequestVerificationToken,you need post the data by form instead of passing it by json.So change data: { currentLocation: location } to data: location like below:
var button = $("#buttonSubmit");
button.on('click', function () {
var location = $('#locationForm').serializeArray();
console.log(location);
$.ajax({
type: "POST",
url: "/Map/MapLocation?handler=AsyncMyLocationAjax",
headers: { "RequestVerificationToken": $('input[name="__RequestVerificationToken"]').val() },
data: location,
success: function (response) {
alert(response);
}
});
});
Result:

How to passing selected value of DDL from Action to another Action MVC/Ajax

I'am trying pass selected value of dropdownlist from one action (RMA) which i have my dropdownlist, to another action (ProcessRequestRMA) which i want send value of dropdownlist and than save it into database using Ajax.
beacuse of that i make instance of that viewmodel (OrdreDetails_VM) which is contains property of dropdown to another viewmodel (RMAHistory) which i want to get selected value and save into database, but when i try to save into database i get Object reference is not set to an instance of an object (under var RMA = new RMA_History). and its beacuse that property should get selected value its null . its been hours im struggling with this , but still no luck :(
Can anyone please help me or point me in the right direction :)
Thanks in advance :)
OrdreDetails_VM & RMA Action :
public class OrdreDetails_VM
{
public List<SelectListItem> RMAType { set; get; }
public int SelectedRMAType { set; get; }
}
public ActionResult RMA(OrdreDetails_VM oodvm)
{
//DDL
oodvm.RMAType = new SelectList(data.RMAType, "ID", "RMASager").ToList();
// do some another stuff
return View(oodvm);
}
RMAHistory_VM & ProcessRequestRMA :
public class RMAHistory_VM
{
public OrdreDetails_VM VM { get; set; }
public int RMAIDType { get; set; }
public string RMASager { get; set; }
public string Kundenavn { get; set; }
public string Ordrenummer { get; set; }
}
public JsonResult ProcessRequestRMA(RMAHistory_VM model)
{
var RMA = new RMA_History // its Modal
{
Kundenavn = model.Kundenavn,
Ordrenummer = model.Ordrenummer,
//Expect to get selected value
RMATypeID = model.VM.SelectedRMAType
};
db.RMA_History.Add(RMA);
db.SaveChanges();
return Json(model, JsonRequestBehavior.AllowGet);
}
//Here is RMAHistory Modal:
public class RMA_History
{
public int Id { get; set; }
public string Kundenavn { get; set; }
public string Ordrenummer { get; set; }
public int? RMATypeID { get; set; }
}
public RMA_HistoryMap()
{
//RMA_History Mapping stuff
}
View:
#model NameSpace.OrdreDetails_VM
//DropDown
#Html.DropDownListFor(s => s.SelectedRMAType, Model.RMAType, "- Select -", new { #class = "form-control", #id = "SelectedRMAType" })
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label>KundeNavn</label>
<input name="Kundenavn" type="text" id="Kundenavn" class="form-control">
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label>Ordrenummer</label>
<input name="Ordrenummer" id="Ordrenummer" type="text" class="form-control" >
</div>
</div>
</div>
AJAX:
<script>
$(document).ready(function () {
$("#btn").click(function (e) {
e.preventDefault();
return myfunction();
});
function myfunction() {
var model = {
Kundenavn: $("#Kundenavn").val(),
Ordrenummer: $("#Ordrenummer").val(),
SelectedRMAType: $("#SelectedRMAType").val()
}
$.ajax({
type: 'POST',
url: "/Account/ProcessRequestRMA",
dataType: 'json',
data: {
Kundenavn: model.Kundenavn,
Ordrenummer: model.Ordrenummer,
RMATypeID: model.SelectedRMAType
},
success: function (status) {
if (status) {
status.Kundenavn = model.Kundenavn;
status.Ordrenummer = model.Ordrenummer;
status.RMATypeID = model.SelectedRMAType;
console.log("Send");
}
else {
alert("Something Wrong");
}
},
error: function () {
console.log('something went wrong - debug it!');
}
});
}
});
</script>
The main problem here is that your View is not bound to the correct ViewModel. You are saying that your view works with a OrdreDetails_VM when it in facts works with a RMAHistory_VM. Temporary data, just as the one your RMAType property represents, should either go into the same ViewModel when you have one, or as part of the ViewBag when you don't want to create one.
So, let's start by first updating RMAHistory_VM to have the required data:
public class RMAHistory_VM
{
public int SelectedRMAType { get; set; }
public string RMASager { get; set; }
public string Kundenavn { get; set; }
public string Ordrenummer { get; set; }
public List<SelectListItem> RMATypes { set; get; }
}
Then, let's throw away OrdreDetails_VM and make your view use RMAHistory_VM correctly:
#model NameSpace.RMAHistory_VM
#Html.DropDownListFor(s => s.SelectedRMAType, Model.RMATypes, "- Select -", new { #class = "form-control", #id = "SelectedRMAType" })
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label>KundeNavn</label>
<input name="Kundenavn" type="text" id="Kundenavn" class="form-control">
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<label>Ordrenummer</label>
<input name="Ordrenummer" id="Ordrenummer" type="text" class="form-control" >
</div>
</div>
</div>
Notice that your AJAX call also does not conform to the ViewModel structure (you are even creating an object you are not using), so you have to update it accordingly:
function myfunction() {
var model = {
Kundenavn: $("#Kundenavn").val(),
Ordrenummer: $("#Ordrenummer").val(),
SelectedRMAType: $("#SelectedRMAType").val()
};
$.ajax({
type: 'POST',
url: "/Account/ProcessRequestRMA",
dataType: 'json',
data: model,
success: function (status) {
if (status) {
status.Kundenavn = model.Kundenavn;
status.Ordrenummer = model.Ordrenummer;
status.RMATypeID = model.SelectedRMAType;
console.log("Send");
}
else {
alert("Something Wrong");
}
},
error: function () {
console.log('something went wrong - debug it!');
}
});
};
And finally update the Action so that it loads ViewBag.RMATypes:
public ActionResult RMA()
{
//DDL
var model = new RMAHistory_VM
{
RMATypes = new SelectList(data.RMAType, "ID", "RMASager").ToList();
};
// do some another stuff
return View(model);
}
You will then have to update how you process the request to match the new ViewModel structure:
public JsonResult ProcessRequestRMA(RMAHistory_VM model)
{
var RMA = new RMA_History // its Modal
{
Kundenavn = model.Kundenavn,
Ordrenummer = model.Ordrenummer,
RMATypeID = model.SelectedRMAType
};
db.RMA_History.Add(RMA);
db.SaveChanges();
return Json(model, JsonRequestBehavior.AllowGet);
}

How can I dynamically add data to a List property in view model in a razor view?

I have a client needs to be able to create candidates. Candidates can have many qualifications (qualifications is model with 4 properties). The client needs to be able to add N numbers of qualifications to the employee on the creation page.
View Model
public class CreateCandidateViewModel
{
[DisplayName("First Name"), Required]
public string FirstName { get; set; }
[DisplayName("Last Name"), Required]
public string LastName { get; set; }
[DisplayName("Email Address"), Required]
public string Email { get; set; }
[DisplayName("Phone Number"), Required]
public string Phone { get; set; }
[DisplayName("Zip Code"), Required]
public int ZipCode { get; set; }
public List<Qualification> Qualifications { get; set; }
}
Qualification Model
public class Qualification
{
[Key]
public int Id { get; set; }
public int QualificationTypeId { get; set; }
public string Name { get; set; }
public DateTime DateStarted { get; set; }
public DateTime DateCompleted { get; set; }
[ForeignKey("QualificationTypeId")]
public virtual QualificationType Type { get; set; }
}
I have no idea how to approach this problem. I was thinking of creating the candidate first and then sending the client to another view where the client can add qualifications and so on.
Stephen mentioned in a comment that you will likely need to use javascript or jquery to accomplish this if you want to keep your user on the same page.
I am assuming your post controller is expecting a CreateCandidateViewModel
It is possible to do model binding when your model has a property of a list of objects, so long as the naming on the inputs is correct when the form is submitted. The key is indexing the names on the inputs:
<input type="text" name="Qualifications[0].Id" />
<input type="text" name="Qualifications[0].QualificationTypeId" />
<input type="text" name="Qualifications[0].Name" />
<input type="text" name="Qualifications[0].DateStarted" />
<input type="text" name="Qualifications[0].DateCompleted" />
<input type="text" name="Qualifications[1].Id" />
<input type="text" name="Qualifications[1].QualificationTypeId" />
<input type="text" name="Qualifications[1].Name" />
<input type="text" name="Qualifications[1].DateStarted" />
<input type="text" name="Qualifications[1].DateCompleted" />
This will correctly bind to your model on submit. Be mindful of resetting the indexies when removing a "qualification" after it was added, or you may get null objects in your list, or missing objects. I've done this successfully before with JQuery and regular expressions.
If you want to go the Ajax way, you could create a partial view and call that using AJAX.
Controller
public ActionResult QualificationsPartial(Int32 Index)
{
return PartialView(model:Index);
}
Partial View
#model Int32
<input type="text" name="Qualifications[#Model.ToString()].Id" />
<input type="text" name="Qualifications[#Model.ToString()].QualificationTypeId" />
<input type="text" name="Qualifications[#Model.ToString()].Name" />
<input type="text" name="Qualifications[#Model.ToString()].DateStarted" />
<input type="text" name="Qualifications[#Model.ToString()].DateCompleted" />
Ajax on Main View
var QualificationIndex = parseInt(1); // 1 because we already have 0 loaded
$("#AddQualificationElement").click(function () {
$.ajax({
cache: false,
type: "GET",
url: "/MyController/QualificationsPartial",
data: {
Index: QualificationIndex
},
success: function (data) {
// data will be the html from the partial view
$("#QualificationsContainer").append(data);
},
error: function (xhr, ajaxOptions, thrownError) {
// Handle the error somehow
}
}); // end ajax call
}); // end AddQualificationElement click event

MVC 4 Model Binding returns null

I'm having trouble with model binding in MVC. I have a class:
public class UserSurvey
{
public int Id { get; set; }
public virtual Survey Survey { get; set; }
}
Which is the model for a view:
#model SurveyR.Model.UserSurvey
<form id="surveyForm">
<div class="container survey">
#Html.HiddenFor(x=>x.Id)
#Html.EditorFor(x => x.Survey.Steps)
</div>
<input type="button" value="Submit" id="btnSubmit"/>
</form>
And then for the submit the controller takes a class:
public class SurveyResponseViewModel
{
public int Id { get; set; }
public Survey Survey { get; set; }
}
[HttpPost]
public ActionResult Submit(SurveyResponseViewModel surveyResponse)
{
...
}
When I debug the submit the surveyResponse.Survey object is populated as it should be but the surveyResponse.Id value is 0 when it should be 1.
I can see the Id=1 being passed back in the submit but the model binding doesn't seem to hook it up.
Any help would be greatly appreciated!
Kev
EDIT: The rendered html looks like this:
<form id="surveyForm">
<div class="container survey">
<input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="1" />
So yes the value appears there and is also passed in the submit if I look using dev tools.
EDIT 2: The Form data in dev tools definitely contains "Id:1".
Your Code seems to be fine.Try passing the id value explicitly as another parameter like below
[HttpPost]
public ActionResult Submit(SurveyResponseViewModel surveyResponse , int Id )
{
surveyResponse.Id = Id
}
I have tested. Its working fine.
public ActionResult test1()
{
var model = new UserSurvey();
model.Id = 10;
return View(model);
}
[HttpPost]
public ActionResult test1(SurveyResponseViewModel surveyResponse)
{
var x = surveyResponse.Id; // returns 10
return View(new UserSurvey());
}
public class SurveyResponseViewModel
{
public int Id { get; set; }
public Survey Survey { get; set; }
}
public class UserSurvey
{
public int Id { get; set; }
public virtual Survey Survey { get; set; }
}
public class Survey
{
public string Steps { get; set; }
}
#model TestWeb.Controllers.UserSurvey
#using (Html.BeginForm())
{
<div class="container survey">
#Html.HiddenFor(x=>x.Id)
#Html.EditorFor(x => x.Survey.Steps)
</div>
<input type="submit" value="Submit" id="btnSubmit"/>
}

Categories