Why controllers action doesn't send json data in response - ajax

I learn ASP.NET MVC and have some issue.
I create two links by ajax helper method in view. They send mailsType argument and response json data. Here is the code of links and AjaxOptions object
<div class="list-group" style = "text-align: center; margin-top: 10px;">
#Ajax.ActionLink("Incoming", "GetMails", new { mailsType = State.Incoming }, opts,
new{#class = "list-group-item active", data_type = "Incoming"})
#Ajax.ActionLink("Outgoing", "GetMails", new {mailsType = State.Outgoing}, opts,
new { #class = "list-group-item", data_type = "Outgoing" })
</div>
Here is ajaxoption, using in ajax helpers
AjaxOptions opts = new AjaxOptions
{
OnSuccess = "viewMail",
};
I handle response by javascript function
function viewMail(data) {
var html =
'<table class="table table-hover table-striped">' +
'<thead>' +
'<tr><th>' + (data.State == 'Incoming' ? 'From' : 'To') + '</th> <th>Subject</th> <th>Date</th></tr>' +
'</thead>' +
'<tbody>';
$(data.Mails).each(function(i, el) {
html += '<tr><td>' + el.From + '</td> <td>' + el.Subject + '</td> <td>' + el.Date + '</td></tr>';
});
html += '</tbody></table>';
$('#contentDiv').html(html);
}
And here is code of action method in controller
public ActionResult GetMails(State mailsType)
{
if (Request.IsAjaxRequest())
{
return Json(new
{
State = Enum.GetName(typeof (State), mailsType),
Mails = _serviceManager.GetMessages(mailsType)
}, "application/json", JsonRequestBehavior.AllowGet);
}
else
{
ViewBag.State = Enum.GetName(typeof(State), mailsType);
return PartialView(_serviceManager.GetMessages(mailsType));
}
}
When I use second link all works good, but when I use first one, i have internal server error with code 500 and response type is text/html.
I use this links to return partialviews and they all works before.
I tried to use my own ajax request, but result was still the same. I can't understand what's wrong. I understan't that question is abstract, but may be you had same problem or know why it happens.
Edit1
When I write address from link in address bar, I have this error:
System.InvalidOperationException: Timeouts are not supported on this stream.
I tryed set big timeout in ajax request, but it didn't help. Reqest executes successfuly when I send less data in response.
I think it related with size of data sending in response. Am I wrong? What reasons of this error can be?
I will by happy all yours advice. Thanks to all!

Related

Why Does This AJAX.Helper Post call get a Enity Framework Error, but the "Get" doesn't?

As a learning project, I have a MVC & Typescript project and a Web 2.0 & entity framework project, the MVC project is trying to talk to the Web 2.0 project and I have a weird error.
This is my Web API 2.0 Player Controller:
public class PlayerController : ApiController
{
// GET api/<controller>/5
public Player Get(int? id)
{
if (id == null || id == -1)
{
var player = new Player();
LeaderBoardContext.Current.Players.Add(player);
LeaderBoardContext.Current.SaveChanges();
return player;
}
return LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == id);
}
// PUT: api/Scores/5
[ResponseType(typeof(void))]
public IHttpActionResult PostPlayer(LearningCancerAPICalls.Models.Player player)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var model = LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == player.PlayerId);
LeaderBoardContext.Current.Entry<Player>(player).State = EntityState.Modified;
try
{
LeaderBoardContext.Current.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
}
return StatusCode(HttpStatusCode.NoContent);
}
}
Its gone through a few iterations by this point, at one point it was initialising its own DB context at the top of the file but that was mysteriously null during the post. So now i'm using the style that we use in other projects which looks like this:
public static LeaderBoardContext Current
{
get
{
try
{
//added because 'HttpContext.Current.Items["_EntityContext"] ' was mysteriously comming back null....
if (HttpContext.Current.Items["_EntityContext"] == null)
{
HttpContext.Current.Items["_EntityContext"] = new LeaderBoardContext();
}
var obj = HttpContext.Current?.Items["_EntityContext"] as LeaderBoardContext;
return obj;
}
catch (Exception) //should only get here if using background task
{
return null;
}
}
}
So the first weirdness is in the post the context insisted on being null, but forcing it not to be null through the convoluted method above hasn't improved the situation much. Notice the first EF call that I have now put in to basically be the same as the GET:
var model = LeaderBoardContext.Current.Players.FirstOrDefault(x => x.PlayerId == player.PlayerId);
I have called the GET in both styles (with -1, with valid ID) and it works fine, but the POST has so far led to this error:
Which I would usually associate with a badly initialised EF project, but the GET works! it does exactly what it should do. I have even tried posting to a EF scafold controller with a different model and had the same problem!
The major difference between the two (apart from GET/POST) is the way I call them, this is how I use the GET:
var playerId = -1;
var activeUser:Player;
function initPlayerOnGameStart() {
if (host === undefined) {
host = 'http://localhost:52316';
}
if (playerId === undefined) {
playerId = -1;
}
var uri = host + '/api/Player/' + playerId;
jQuery.getJSON(uri).done(data => {
activeUser = data;
playerId = activeUser.PlayerId;
});
}
In a pure Typescript Json call. To do the POST I am experimenting with AJAX.Helper:
#model LearningCancerAPICalls.Models.Player
<a id="contact-us">Share Score!</a>
<div id="contact-form" class="hidden" title="Online Request Form">
#using (Ajax.BeginForm("", "", null, new AjaxOptions
{
HttpMethod = "POST", Url = "/api/Player",
OnSuccess ="OnSuccess",
OnFailure ="OnFailure"
}, new { id = "formId", name = "frmStandingAdd" }))
{
#Html.LabelFor(m => m.PlayerName);
#Html.TextBoxFor(m => m.PlayerName);
#Html.LabelFor(m => m.Email);
#Html.TextBoxFor(m => m.Email);
#Html.HiddenFor(m => m.PlayerId);
#Html.Hidden( "PlayerId");
<input type="submit" name="submit" value="Ok" />
}
</div>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script>
function OnSuccess() {
alert('Success');
}
function OnFailure(ajaxContext) {
alert('Failure');
}
</script>
Where I set PlayerID from the typescript. This successfully calls the post but crashes on the first use of EF. The other peculiar thing is that if I put a debug on the post. The model doesnt seem correct, as in, when I hover over it, it shows itself as a Player model, there has been no casting error, but it does not let me expand its properties. If I use variables or the imediate window to inspect variables then they are all fine. But I thought it was worth mentioning.
I am going to try a pure ajax call later to see if it resolves it, but I don't understand why the Ajax.helper would be at fault here, it technically does its job and the error is not related to the model that I can see.
UPDATE 1
So I tried the pure ajax call:
Html:
Name: <input type="text" name="fname" id="userName"><br />
<button onclick="postJustPlayer()"> Ok </button>
Typescript
function postJustPlayer() {
let level = jQuery("#chooseGridLevel").val();
let name = jQuery("#userName").val();
let uri = host + '/api/Player';
let player: Player = <Player>{};
player.Email = "Testing";
player.PlayerName = name;
jQuery.post(uri, player);
}
And this WORKS!?? I have no idea why the pure jQuery works, surely as far as EF is concerned it does the exact same thing? why would an AJAX.helper post be any different...
Solved it! This was a true puzzle, only solved when I delved into the network data (tools ftw).
For other newbies to web stuff I will explain how I found the route of this problem. In Chrome Dev Tools there is a Network tab that will show your web requests & responses. So by opening it after clicking my OK Button I can see this for my pure AJAX call:
I could then compare this to when I clicked "Submit" on my ajax form:
I Copy and paste these both into KDiff3, which highlighted one VERY important difference the local host address!
You will notice in the pure ajax request I specified the host, this is because as I mentioned, my web api project and my website project are separate, therefore they are on separate hosts!
So, in reality, the AJAX helper call should never have worked, but as it happens the day before I decided I needed a model from my API project in my website project and at the time thought "I probably shouldn't include my API project as a reference in my main website, but just for now....". So this lead to the API call with the wrong host being valid! With of course the fundamental difference that EF was not set up on THAT host.
So poor old ajax helper got plenty of my cursing for an error that only a special kind of idiot set up could lead to. Changing ajax helper to use the full path:
#model LearningCancerAPICalls.Models.Player
<a id="contact-us">Share Score!</a>
<div id="contact-form" class="hidden" title="Online Request Form">
#using (Ajax.BeginForm("", "", null, new AjaxOptions
{
HttpMethod = "POST", Url = "http://localhost:52316/api/Player",
OnSuccess ="OnSuccess",
OnFailure ="OnFailure"
}, new { id = "formId", name = "frmStandingAdd" }))
{
#Html.LabelFor(m => m.PlayerName);
#Html.TextBoxFor(m => m.PlayerName);
#Html.LabelFor(m => m.Email);
#Html.TextBoxFor(m => m.Email);
#Html.HiddenFor(m => m.PlayerId);
#Html.Hidden( "PlayerId");
<input type="submit" name="submit" value="Ok" />
}
</div>
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
<script>
function OnSuccess() {
alert('Success');
}
function OnFailure(ajaxContext) {
alert('Failure');
}
</script>
Solved the problem! Thank you for anyone who scratched their head over this one, hopefully, this breakdown of a weird bug will be useful to someone.

MVC 4 Ajax.BeginForm and ModelState.AddModelError

I'm trying to get errors to show up after an ajax submit has returned an error. I'm not sure what I'm missing, but I can't get it to work. This question is basically the same thing - ModelState.AddModelError is not being displayed inside my view but I'm still not having any luck. My experience with Ajax and MVC (any version) is still a bit limited. Here is a very simple example, most of which I took from the previous link.
View: test.cshtml
#model TestProject.VisitLabResult
#Scripts.Render("~/Scripts/jquery.unobtrusive-ajax.min.js")
#Scripts.Render("~/Scripts/ckeditor/ckeditor.js")
#{
AjaxOptions ajaxOpts = new AjaxOptions
{
Url = Url.Action("test"),
HttpMethod = "Post",
LoadingElementId = "loading",
LoadingElementDuration = 500,
OnSuccess = "processData"
};
}
#Html.ValidationMessage("CustomError")
<div id="loading" class="load" style="display:none">
<p>Saving...</p>
</div>
<table>
#for (int item = 0; item < 10; item++)
{
<tr id = #item>
#using (Ajax.BeginForm(ajaxOpts))
{
#Html.ValidationSummary(true)
#Html.AntiForgeryToken()
<td>
<input type="submit" value="Create" />
</td>
<td id = #(item.ToString() + "td")>
</td>
}
</tr>
}
</table>
Controller: HomeController.cs
public ActionResult test()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult test(VisitLabResult vlr, int visitid = 28)
{
try
{
if (ModelState.IsValid)
{
if (Request.IsAjaxRequest())
{
throw new Exception("error");
}
else
return View(vlr);
}
else
return View(vlr);
}
catch (Exception ex)
{
ModelState.AddModelError("CustomError", "The Same test Type might have been already created, go back to the Visit page to see the available Lab Tests");
return View(vlr);
}
}
Model
public class VisitLabResult
{
public int visitid { get; set; }
}
If it is an Ajax request I throw an error and it's caught and an error is added to ModelState. That error never shows up on the page though. Am I approaching this the right way at all? Or do I need to take a different route? I appreciate any help.
Just to clarify the solution for other people hitting this question. The ajax helper fires OnSuccess vs OnFailure based on the returned HTTP Code per the AjaxOptions docs:
OnSuccess: This function is called if the response status is in the 200 range.
OnFailure: This function is called if the response status is not in the 200 range.
In other words, you have to manually specify that there was a failure when returning your ActionResult by changing the Response.StatusCode and then returning whatever values you're expecting in your OnFailure js method. You can drive that based on any business logic you want (i.e. catch Exception ex) or !ModelState.IsValid ...)
[HttpPost]
public ActionResult Search(Person model)
{
if (ModelState.IsValid) {
// if valid, return a HTML view inserted by AJAX helper
var results = PersonRepository.Get(model)
return PartialView("Resulsts", vm);
} else {
// if invalid, return a JSON object and handle with OnFailure method
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json(new { errors = ModelState.Values.SelectMany(v => v.Errors) });
}
}
Further Reading:
MVC 4 Ajax.BeginForm and ModelState.AddModelError
ASP.NET MVC “Ajax.BeginForm” executes OnSuccess even though model is not valid

Spring dojo request issue

I am quite new to Spring framework and i have a problem.
I have a page A.jsp and in this page i have a link to page B.jsp
<c:url value="${pageContext.request.contextPath}" var="contextPath" />
Click here
And in controller
#RequestMapping("pageB")
public String pageBlink(SitePreference sitePreference, Device device, Model model) {
return "pageB";
}
Now on page B.jsp i want to invoke an Ajax call.
I have a link Send request
function myFunction(){
dojo.xhrGet({
// The URL of the request
url: "requestPage",
method: "POST",
handleAs: "json",
// The success callback with result from server
load: function(jsonData) {
var content = "";
dojo.forEach(jsonData.newsItems,function(locationPoint) {
// Build data from the JSON
content += "<p>" + locationPoint.name + "</p>";
content += "<p>" + locationPoint.latitude + "</p>";
content += "<p>" + locationPoint.longitude + "</p>";
content += "<p>" + locationPoint.number + "</p>";
});
},
// The error handler
error: function() {
// Do nothing -- keep old content there
},
// generate an extra GET variable to prevent browsers from caching
preventCache: true
});
}
And add into controller
#RequestMapping(value="requestPage", method = RequestMethod.GET)
public MyObj returnEVSELocations(){
logger.log(Level.INFO, "return evse locations --------------");
MyObj myObj = new MyObj();
// add some stuff into the obj
return myObj;
}
But this request a requestPage.jps ... i want just to work in my page (B.jsp).
Any help is more than welcome.
Thanks!
I found the issue.
In fact there was 2 problems
1. In Ajax call i must have
dojo.forEach(jsonData, ...) instead dojo.forEach(jsonData.newsItems, ...)
2. In the controller in on my method i must add the annotation
public #ResponseBody MyObj
I hope this help someone to front the same issue.

Calling multiple action methods (using ajax) and showing the result of last in a new tab

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.

MVC3 Routing problem HTTP 404

I'm at a loss trying to figure out why I have Actions that are returning 404 'The Resource cannot be found' errors.
Controller Name: ItemManagementController
My Index view has list of items in a table. Each row contains two links, 'Delete' and 'Request Update'. The Delete link calls a Delete action and works fine. The Request Update gives me the 404 error, and seems as if trying to navigate to a URL like http://localhost/TVAPDev/ItemManagement/RequestUpdate?itemID=9.
I have to assume I'm missing something simple, as they are identical in what they do from the view aspect. The actions as defined in the controller are both similar except that they call different methods on a service layer, but that's it.
Here are my two Controller Actions.
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult Delete(int itemID) {
var svc = new ItemManagementService(_repository);
var requestModel = svc.GetItemDeleteModel(itemID);
svc.DeleteItem(requestModel);
var message = requestModel.ActionMessage;
return Json(new { id = itemID, ChangeStatus = requestModel.ItemDetails.ItemChangeStatus.ToString(), ChangeType = requestModel.ItemDetails.ItemChangeType.ToString(), message});
}
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult RequestUpdate(int itemID) {
var svc = new ItemManagementService(_repository);
var requestModel = svc.GetItemUpdateRequestModel(itemID);
svc.RequestItemUpdate(requestModel);
var message = requestModel.ActionMessage;
return Json(new { id = itemID, ChangeStatus = requestModel.ItemDetails.ItemChangeStatus.ToString(), ChangeType = requestModel.ItemDetails.ItemChangeType.ToString(), message });
}
Here are the links as they are defined in the View
<td class="tblist" style="white-space: nowrap;">
#Html.ActionLink("Request Update", "RequestUpdate", new { itemID = item.ItemID }, new AjaxOptions {
HttpMethod = "POST",
Confirm = "Request an Update to this item?",
OnSuccess = "actionCompleted"
})break;
}
</td>
<td class="tblist" style="white-space: nowrap;">
#Ajax.ActionLink("Delete", "Delete", new { itemID = item.ItemID }, new AjaxOptions {
HttpMethod = "POST",
Confirm = "Are you sure you want to delete this Item?",
OnSuccess = "actionCompleted"
})
</td>
Again, the Delete here works without issue. The Request Update link gives me the Http 404 error.
Anyhelp here would be greatly appreciated.
Why are you using AjaxOptions on a normal Html.ActionLink (which is what Request Update is)?
Maybe you wanted it to be like this:
#Ajax.ActionLink(
"Request Update",
"RequestUpdate",
new {
itemID = item.ItemID
},
new AjaxOptions {
HttpMethod = "POST",
Confirm = "Request an Update to this item?",
OnSuccess = "actionCompleted"
}
)
Check your View code... the delete is using the Ajax html helper and the update is using the regular html helper.

Resources