I have written an Ajax POST to submit a form, but it fails to succeed.
The Ajax POST comes through to the action in the controller which in turn will return a PartialView, which is also made correctly as I can debug this.
The goal is to let the user add a new log, whatever the outcome (failed captcha, failed validation) a Partialview will be returned with the right ViewBag errors messages. Razor takes care of the rest. This way the user will not be redirected to other pages.
When the Ajax succeeds it should put the data in the right <div>. The code is a copy of a working Ajax GET only changing it into a POST and providing the formdate serialized.
tldr; Ajax POST to Action in controller works, the partialview is rendered and returned yet the Ajax failes to succeed making it unable to update the designated <div>
Ajax call:
<script>
$('#add-log').click(function (event) {
event.preventDefault();
$.ajax({
url: $('#add-log').attr('data-url'),
type: 'post',
data: $("#log-form").serialize(),
succes: function (data) {
$('#add-log').attr('data-target').html(data);
console.log("Succes");
}
});
}
</script>
Controller:
[HttpPost, ValidateInput(false)]
[Authorize(Roles = "Student")]
public ActionResult Add(object sender, Log log, string returnURL, bool SendEmail)
{
ViewBag.Vulns = TempData["Vulns"];
//region Captcha: Here we have our Captcha settings
var response = Request["g-recaptcha-response"];
//secret that was generated in key value pair
const string secret = "Just a secret for our captcha, move along";
var client = new WebClient();
var reply = client.DownloadString(string.Format("https://www.google.com/recaptcha/api/siteverify?secret={0}&response={1}", secret, response));
var captchaResponse = JsonConvert.DeserializeObject<CaptchaResponse>(reply);
if (ModelState.IsValid && captchaResponse.Success)
{
db.Log.Add(log);
db.SaveChanges();
if (SendEmail)
{
//Emails are sent from here on out, but that's out of the scope
}
}
else
{
if (!captchaResponse.Success)
{
ViewBag.fillcaptcha = "Please fill in the captcha";
}
else
{
ViewBag.Wrong = "Something went wrong please try again";
}
ViewBag.returnUrl = returnURL;
ViewBag.domain = log.DomainId;
return PartialView(log);
}
}
There's more code but I left that out. Most important thing in the controller is just the last IF. I test the ajax by posting an empty log with no captcha resulting in a partialview with the same log and the viewbag.captcha error. This partialview is rendered but somehow ajax doesn't succeed.
Related
I am new in asp.net mvc programming, please be gentle... :)
Please notice that the following views are all PARTIAL views! Methods are called through Ajax and redirect to partial views with lists, forms are posted through Ajax, etc. OK, here we go...
1st controller named AlertsController. One of the methods is ResolveAlert(Guid id) which returns RedirectToAction -> UnresolvedAlerts() which is just a list of unresolved alerts.
2nd contoller named FrontDeskController. One of the methods is CustomerDetails(Guid id) which lists the customer and alerts that he might have.
I want to be able to "Resolve an alert" (thus use the method of the 1st controller) but return to the page that I was before instead of going to the redirected page that the method returns.
I added a second parameter to the ResolveAlert() method which lists a returnUrl string. I manage to send the Url that I want it to redirect to but I get just the partial (not rendered inside the whole page as it should)...
Here's my ResolveAlert method on my AlertsController:
// Resolve Alert POST
[HttpPost]
public async Task<ActionResult> Resolve(AlertModel model, string redirectUrl)
{
await _AlertsService.ResolveAsync(model);
if (!string.IsNullOrWhiteSpace(redirectUrl))
return Redirect(redirectUrl);
return RedirectToAction("Unresolved");
}
...and here is my CustomerDetails() method on my FrontDeskController:
// Display Customer Alerts
public async Task<PartialViewResult> CustomerDetails(AttendanceModel model, Guid id)
{
var customer = await _CustomersService.ReadAsync(id);
ViewData["Customer"] = await _CustomersService.ReadCustomerExtendedAsync(id);
var alerts = await _AlertsService.ReadCustomerAlertsAsync(id);
ViewData["Alerts"] = alerts.Where(x => x.IsResolved == false).ToList();
return PartialView("_CustomerDetails", model);
}
The ResolveAlert() method of the first controller is called in two steps... 1st I call a modal from the CustomerDetails view:
function resolveAlert(alertId, customerId) {
var returnTo = '/FrontDesk/CustomerDetails/' + customerId;
$.ajax({
method: 'GET',
url: '/Alerts/Resolve/' + alertId,
data: {returnUrl : returnTo},
dataType: 'html'
}).then(function (html) {
$('#dialog-container').html(html);
showDialog();
});
}
...then on the modal I have:
#{
var data = Request.Params["returnUrl"];
}
#using (Ajax.BeginForm("Resolve", "Alerts", new { redirectUrl = data}, new AjaxOptions() { HttpMethod = "POST", InsertionMode = InsertionMode.Replace, UpdateTargetId = "partial", OnSuccess = "hideDialog" }, new { id = "form", #class = "form-horizontal" }))
{ ..... textbox with some notes that I can post while resolving the alert ..... }
... and (finally) here is the final part at the bottom of my modal:
<script type="text/javascript">
$('#form').validate({
rules: {
AlertNotes: {
required: true
}
},
submitHandler: function (form) {
$.ajax({
url: $(form).attr("action"),
data: $(form).serialize(),
type: $(form).attr("method")
}).then(function (result) {
$("#partial").html(result);
hideDialog();
});
}
});
</script>
I think that in order for the returned partial to get rendered correctly inside its container I should be returning a RedirectToAction from the ResolveAlert() method but the problem is that it belongs on a different controller...
Is it possible to get this working somehow or should I just bite the bullet and forget about having those pages as partials, get rid of the Ajax calls and use normal Url.Action() links?
It was just a Javascript/Ajax bug in my code.... Please disregard the question...
For those wondering, I had 2 forms using the same id. JS died silently allowing the form to be posted normally and not through Ajax. It had me scratching my head for a while now. Too bad that web development tools and VS in particular can't snipe such errors and provide a meaningful hint to assist you in debugging...
I have already read this and this to get some help but there is something wrong in my code. Well I want to insert a form to database via Ajax and this is what i did:
The Ajax function :
<script type="text/javascript">
function doAjaxPost() {
var form = $('#ajf');
frm.submit(function () {
$.ajax({
type: "POST",
url: "${pageContext.request.contextPath}/ajouter_user",
data: form.serialize(),
success: function(response){
// we have the response
$('#info').html(response);
},
error: function(e){
alert('Error: ' + e);
}
});
});
}
</script>
'#info' is the ID of the DIV where i want to show the success message returned by the controller.
this is my controller :
#RequestMapping(value="/ajouter_user",method=RequestMethod.POST)
public #ResponseBody String addUser(#ModelAttribute User us,BindingResult result,ModelMap model){
String returnText;
if(!result.hasErrors()){
model.addAttribute("us", new User());
userservice.AddUser(us);
model.addAttribute("usersystem", userservice.getAllUsers());
return returnText = "User has been added to the list." ;
}else{
return returnText = "Sorry, an error has occur. User has not been added to list.";
}
}
HTML :
<form:form id="ajf" method="POST" commandName="user">
Here are my fields ...
<input type="submit" value="Créer" onclick="doAjaxPost()"/>
</form:form>
What is wrong is : I don't get the String that the controller return , I get an alert error (object [] object ), the data is inserted to database and the page reload after submitting without giving any error
Can someone give me a toturial how to use Ajax with spring (inserting to database )
please help me
If you are using jQuery (it looks like you are), then you need to include the event in the function and do an event.preventDefault() to prevent the form from submitting. Otherwise the event will propagate up and the form will submit, and you will get the ajax post and the form post.
I have a form in which I need to call two action methods, one after the other. This is how the flow goes.
First I check if the prerequisite data is entered by the user. If not then I show a message that user needs to enter the data first.
If all the prerequisite data is entered, I call an action method which return data. If there is no data returned then I show a message "No data found" on the same page.
If data is returned then I call another action method present in a different controller, which returns a view with all the data, in a new tab.
The View:
#using (Ajax.BeginForm("Index", "OrderListItems", null, new AjaxOptions { OnBegin = "verifyRequiredData"}, new { #id = "formCreateOrderListReport", #target = "_blank" }))
{
//Contains controls and a button
}
The Script in this View:
function verifyRequiredData() {
if ($("#dtScheduledDate").val() == "") {
$('#dvValidationSummary').html("");
var errorMessage = "";
errorMessage = "<span>Please correct the following errors:</span><ul>";
errorMessage += "<li>Please enter Scheduled date</li>";
$('#dvValidationSummary').append(errorMessage);
$('#dvValidationSummary').removeClass('validation-summary-valid').addClass('validation-summary-errors');
return false;
}
else {
$('#dvValidationSummary').addClass('validation-summary-valid').removeClass('validation-summary-errors');
$('#dvValidationSummary').html("");
$.ajax({
type: "GET",
url: '#Url.Action("GetOrderListReport", "OrderList")',
data: {
ScheduledDate: $("#dtScheduledDate").val(),
Crews: $('#selAddCrewMembers').val(),
Priorities: $('#selPriority').val(),
ServiceTypes: $('#selServiceTypes').val(),
IsMeterInfoRequired: $('#chkPrintMeterInfo').val()
},
cache: false,
success: function (data) {
debugger;
if (data !== "No data found") {
//var newUrl = '#Url.Action("Index", "OrderListItems")';
//window.open(newUrl, '_blank');
return true;
} else {
//Show message "No data found"
return false;
}
}
});
return false;
}
}
The "GetOrderListReport" Action method in "OrderList" Controller:
public ActionResult GetOrderListReport(OrderListModel model)
{
var contract = new OrderReportDrilldownParamDataContract
{
ScheduledDate = model.ScheduledDate
//Setting other properties as well
};
var result = OrderDataModel.GetOrderList(contract);
if (string.IsNullOrWhiteSpace(result) || string.IsNullOrEmpty(result))
{
return Json("No data found", JsonRequestBehavior.AllowGet);
}
var deserializedData = SO.Core.ExtensionMethods.DeserializeObjectFromJson<OrderReportDrilldownDataContract>(result);
// send it to index method for list
TempData["DataContract"] = deserializedData;
return Json(deserializedData, JsonRequestBehavior.AllowGet);
}
The last action method present in OrderListItems Controller, the result of which needs to be shown in a new tab:
public ActionResult Index()
{
var deserializedData = TempData["DataContract"] as OrderReportDrilldownDataContract;
var model = new OrderListItemViewModel(deserializedData);
return View(model);
}
The problem is that I am not seeing this data in a new tab, although I have used #target = "_blank" in the Ajax.BeginForm. I have also tried to use window.open(newUrl, '_blank') as can be seen above. But still the result is not shown in a new tab.
Please assist as to where I am going wrong?
If you are using the Ajax.BeginForm you shouldn't also be doing an ajax post, as the unobtrusive ajax library will automatically perform an ajax post when submitting the form.
Also, if you use a view model with data annotation validations and client unobtrusive validations, then there would be no need for you to manually validate the data in the begin ajax callback as the form won't be submitted if any validation errors are found.
The only javascript code you need to add in this scenario is a piece of code for the ajax success callback. That will look as the one you currently have, but you need to take into account that opening in new tabs depends on the browser and user settings. It may even be considered as a pop-up by the browser and blocked, requiring the user intervention to allow them as in IE8. You can give it a try on this fiddle.
So this would be your model:
public class OrderListModel
{
[Required]
public DateTime ScheduledDate { get; set; }
//the other properties of the OrderListModel
}
The form will be posted using unobtrusive Ajax to the GetOrderListReport of the OrderList controller. On the sucess callback you will check for the response and when it is different from "No data found", you will then manually open the OrderListItems page on a new tab.
This would be your view:
#model someNamespace.OrderListModel
<script type="text/javascript">
function ViewOrderListItems(data){
debugger;
if (data !== "No data found") {
var newUrl = '#Url.Action("Index", "OrderListItems")';
//this will work or not depending on browser and user settings.
//passing _newtab may work in Firefox too.
window.open(newUrl, '_blank');
} else {
//Show message "No data found" somewhere in the current page
}
}
</script>
#using (Ajax.BeginForm("GetOrderListReport", "OrderList", null,
new AjaxOptions { OnSucces= "ViewOrderListItems"},
new { #id = "formCreateOrderListReport" }))
{
#Html.ValidationSummary(false)
//input and submit buttons
//for inputs, make sure to use the helpers like #Html.TextBoxFor(), #Html.CheckBoxFor(), etc
//so the unobtrusive validation attributes are added to your input elements.
//You may consider using #Html.ValidationMessageFor() so error messages are displayed next to the inputs instead in the validation summary
//Example:
<div>
#Html.LabelFor(m => m.ScheduledDate)
</div>
<div>
#Html.TextBoxFor(m => m.ScheduledDate, new {id = "dtScheduledDate"})
#Html.ValidationMessageFor(m => m.ScheduledDate)
</div>
<input type="submit" value="Get Report" />
}
With this in place, you should be able to post the data in the initial page using ajax. Then based on the response received you will open another window\tab (as mentioned, depending on browser and user settings this may be opened in a new window or even be blocked) with the second page content (OrderListItems).
Here's a skeleton of what I think you are trying to do. Note that window.open is a popup though and most user will have popups blocked.
<form id="formCreateOrderListReport">
<input type="text" vaule="testing" name="id" id="id"/>
<input type="submit" value="submit" />
</form>
<script type="text/javascript">
$('#formCreateOrderListReport').on('submit', function (event) {
$.ajax({
type: "POST",
url: '/home/test',
data: { id: $('#id').val()},
cache: false
}).done(function () {
debugger;
alert("success");
var newUrl = '/home/contact';
window.open(newUrl, '_blank');
}).fail(function () {
debugger;
alert("error");
});
return false;
});
</script>
Scale down the app to get the UI flow that you want then work with data.
I've got a page that contains multiple links. These links should do an ajax post and callback. However, the link is doing a Get instead of a Post. This causes a 404 error since I do not have an action method to handle a get at the requested URL.
If I remove the HTTPPost attribute from my Action method, the link works, but the call back fails and the Json I return is rendered in a new page.
Here is the code I'm using in my view.
<td id="action-#item.ItemID">#Ajax.ActionLink("Add", "AddToOrder", new { itemID = item.ItemID }, new AjaxOptions { HttpMethod = "POST", OnSuccess = "actionCompleted" }, new { id = "add-" + item.ItemID })</td>
This ends up adding this HTML:
<td id="action-012679"><a data-ajax="true" data-ajax-method="POST" data-ajax-success="actionCompleted" href="/mysite/neworder/AddToOrder?itemID=012679" id="add-012679">Add to Order</a></td>
My Controller has the following Action Method.
[HttpPost]
public JsonResult AddToOrder(string itemID) {
return Json(new { id = itemID, Action = "Added", "Just getting this working"});
}
My callback method that is called on Success looks like this:
<script language="javascript" type="text/javascript">
function actionCompleted(response, status, data) {
alert("We have returned");
}
</script>
If I change the [HTTPPost] attribute on my action method to [HTTPGet] I get an Json error. I can fix this by adding the JsonRequestBehavior.AllowGet to my return value, but this doesn't use the call back function defined on the page and fails.
Any help would be appreciated.
Probably you don't have jquery.unobtrusive-ajax.js script attached to page and link is gracefully degraded to regular anchor.
In order to have this working properly. You need to add references to these scripts on your page:
MicrosoftAjax.js
MicrosoftMvcAjax.js
My controller action is being executed twice. Fiddler shows two requests and responses, and for the first one has an icon that indicates "Session was aborted by the client, Fiddler, or the Server."
But I can't figure out where this is happening, or why.
Here are the specifics:
I have a section of a view (ThingFinancials) that looks like this:
#{ using (Html.BeginForm("ConfirmThing", "Thing", null, FormMethod.Get, new { id = "frmGo" }))
{
#Html.HiddenFor(model => model.ThingID)
<button id="btnGo">
Thing is a Go - Notify People</button>
}
}
The javascript for btnGo looks like this:
$("#btnGo").click(function () {
var form = $("#frmGo");
form.submit();
});
The action (stripped down) looks like this:
public ActionResult ConfirmThing(int thingID)
{
[do some database stuff]
[send some emails]
var financials = GetFinancials(thingID);
return View("ThingFinancials", financials);
}
The only thing that looks unusual to me is that the URL you'd see would start out as [Website]/Thing/ThingFinancials/47, and after submission the URL would be [Website]/Thing/ConfirmThing?ThingID=47.
(If you're wondering why the Action name doesn't match the View name, it's because there are multiple form tags on ThingFinancials, and they can't all have the same action name.)
Is there a Server.Transfer happening behind the scenes, or something like that?
If you are using a submit button then you need to cancel the default behaviour when submitting with javascript, otherwise you will submit it twice. Try this:
$("#btnGo").click(function () {
var form = $("#frmGo");
// event.preventDefault(); doesn't work in IE8 so do the following instead
(event.preventDefault) ? event.preventDefault() : event.returnValue = false;
form.submit();
});
Your int thingID is a query string parameter that stays with the request. At the end of ActionResult ConfirmThing(int thingID), all you're doing is returning a view. If you'd rather see the clean URL ([Website]/Thing/ThingFinancials/47) you can make the following changes.
public ActionResult ConfirmThing(int thingID)
{
[do some database stuff]
[send some emails]
// This logic is probably in the 'ThingFinancials' action
// var financials = GetFinancials(thingID);
// I'll assume we're in the same controller here
return RedirectToAction("ThingFinancials", new { thingID });
}
This is because of your jquery event just add stopImmediatePropagation() to your jquery event.
$("#btnGo").click(function (event){
event.stopImmediatePropagation();
});