Retrieving data from XMLHttpRequest on $.Ajax Error Event - asp.net-mvc

As the title says, i want to retrieve my Json data values from a XMLHttpRequest. I already know how to do it by using a common success $.Ajax success event, but i want to get it's values from an error event. A thing that i noticed is not simple to find all kinds of a XMLHttpRequest types.
To explain a little more, here's the scenario: after some inactivity, the user sessions expires. If he tries to do any operations using an Ajax call, he'll be redirected to the login page. I handle this session timeout error on a particular filter that implements an OnException method.
While i can do it using a sort of a hack (by setting manually the HTTP response code), i'd like to do it on "proper way", with no hacks.
OnException method code snippet
filterContext.ExceptionHandled = true;
// If this is an ajax request, return the exception in the response
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
//If the session has expired, throws a SESSION TIMEOUT error
if (Session["UserLogged"] == null)
{
//The hack mentioned before
filterContext.HttpContext.Response.StatusCode = 502;
filterContext.Result = new JsonResult()
{
Data = new { success = false, errorType = "SESSION_TIMEOUT" ,error = filterContext.Exception.ToString() },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
So, by using a simple $.Ajax Error Event, how would i retrive the data from the filterContext.Result? Specifically the errorType parameter.

You should be able to parse the JSON data out of the jqXHR responseText property in your error handler.
error: function(jqXHR, textStatus, errorThrown) {
alert('jqXHR.responseText = ' + jqXHR.responseText);
}

Related

How to make AngularJS throw error instead of silently failing when template cannot be retrieved? [duplicate]

In an AngularJS directive the templateUrl parameter is defined dinamically.
'templates/' + content_id + '.html'
I don't want to establish rules to check if content_id value is valid and manage it as 404 errors, i.e. if the template doesn't exist (server return a 404 error when loading the template) load template/404.html instead.
How can I do that?
Edited: The current answers suggest to use a response error interceptor. In this case ¿how can I know that the response is to a loading of this template?
You will need to write response error interceptor. Something like this:
app.factory('template404Interceptor', function($injector) {
return {
responseError: function(response) {
if (response.status === 404 && /\.html$/.test(response.config.url)) {
response.config.url = '404.html';
return $injector.get('$http')(response.config);
}
return $q.reject(response);
}
};
});
app.config(function($httpProvider) {
$httpProvider.interceptors.push('template404Interceptor');
});
Demo: http://plnkr.co/edit/uCpnT5n0PkWO53PVQmvR?p=preview
You can create an interceptor to monitor all requests made with the $http service and intercept any response errors. If you get a status 404 for any request made, simply redirect the user to error page(template/404.html in your case).
.factory('httpRequestInterceptor', function ($q) {
return {
'responseError': function(rejection) {
if(rejection.status === 404){
// do something on error
}
}
return $q.reject(rejection);
}
};
});
You would need to push the interceptor to $httpProvider in your config function.
myApp.config( function ($httpProvider, $interpolateProvider, $routeProvider) {
$httpProvider.interceptors.push('httpRequestInterceptor');
});
Here's the demo
Cheers!

how to secure or set a rails style before_filter for all angular controllers?

I'm using angularjs for the front end and rails + devise for authentication on the backend.
On the front end I have added a responseInterceptor to redirect to the /#/sign_in page upon any 401 response from any xhr request and display a growl style pop-up message using toastr.
App.config(['$httpProvider', function ($httpProvider) {
$httpProvider.responseInterceptors.push('securityInterceptor');
}]);
App.factory('securityInterceptor', ['$injector', '$location', '$cookieStore', function ($injector,$location,$cookieStore) {
return function(promise) {
var $http = $injector.get('$http');
return promise.then(null, function(response){
if (response.status === 401) {
$cookieStore.remove('_angular_devise_user');
toastr.warning('You are logged out');
$location.path('/#/sign_in');
}
});
};
});
My problem is, when I click on a page that loads several xhr requests during the controllers initialization, for example:
var products = Product.query();
var categories = Category.query();
var variations = Variation.query();
These are needed for various navigation components and they all fire off in parallel, resulting in several duplicate growl-style messages.
Is there a way to make angular quit on the first 401 and stop execution of the rest of the controller from within the interceptor? In a traditional rails app, there would be a "before_filter" that stops regular execution, preventing the page and queries from loading... what's the best way to do this in angular?
I've been pondering about this problem for my own apps too. A sketch of my thoughts (NOT REAL IMPLEMENTATION, SO BEWARE):
A userData service keeps track of whether the user is logged in + other information (e.g. user name, real user name etc):
App.service("userData", function() {
var currentData = {
loggedIn: false
};
function getCurrent() {
return currentData;
}
// called when the user logs in with the user data
function loggedIn(userData) {
// the object is REPLACED to avoid race conditions, see explanation below
currentData = angular.extend({loggedIn: true}, userData);
}
return {
getCurrent: getCurrent,
loggedIn: loggedIn
};
});
The interceptors keep track of the currentData. If an interceptor receives HTTP 401 and the loggedIn flag is true, it changes the flag to false and redirects to the login view. If an interceptor receives HTTP 401 and the loggedIn flag is false, it does nothing besides rejecting the request, because another interceptor has done the view redirection.
When the user logs in, the currentData is replaced, so as to avoid situations with delayed responses (e.g. call1 and call2 are initiated, call1 responds 401; call2 also results in 401, but the delivery of the actual response is delayed; then the user logs in again; then call2 receives its 401; the second 401 should not overwrite the current state)
App.config(["$provide", "$httpProvider", function($provide, $httpProvider) {
$provide.factory("myHttpInterceptor", ["$q", "userData", "$cookieStore", "toastr", "$location",
function($q, userData, $cookieStore, toastr, $location) {
return {
request: function(config) {
config.currentUserData = userData.getCurrent();
return config;
},
responseError: function(rejection) {
if( rejection && rejection.status === 401 && rejection.config && rejection.config.currentUserData && rejection.config.currentUserData.loggedIn ) {
rejection.config.currentUserData.loggedIn = false;
$cookieStore.remove('_angular_devise_user');
toastr.warning('You are logged out');
$location.path('/#/sign_in');
}
return $q.reject(rejection);
}
};
}
]);
$httpProvider.interceptors.push("myHttpInterceptor");
});
Also note I am using the newer way to register interceptors, as $httpProvider.responseInterceptors seems to be deprecated.

How to retrieve exact reason of the error from async HttpRequest?

I am trying to figure out how to find out exact reason of (async) HttpRequest (from 'dart:html') failure, and, to be honest, I am a bit lost here.
The onError callback receives only HttpRequestProgressError object, which doesn't have anything useful, and the HttpRequest object itself has "status" set to "0" in case of failure, even console shows "Failed to load resource" with no details.
What I want is to know the exact reason - like "connection refused" or "host name not resolved".
Is this possible at all?
Thank you!
Unfortunately, there is no property to report the error as detailed as you'd like. The reason is that JavaScript doesn't support this.
There are the properties status and statusText on the HttpRequest object (which you could get from your HttpRequestProgressEvent with evt.target, but those represent HTTP status codes. Every other error has the status code 0 - request failed. This could be anything, and the only place to look at is the browser's console, because this is an Exception thrown by the browser.
If your request was synchronous, you could surround the send() with a try-catch. If your request is async, this won't work.
See here
#library('Request');
#import('dart:html');
#import("dart:json");
typedef void RequestHandler(String responseText);
typedef void ErrorHandler(String error);
class ResourceRequest {
XMLHttpRequest request;
RequestHandler _callbackOnSuccess;
ErrorHandler _callbackOnFailure;
ResourceRequest.openGet(String url, RequestHandler callbackOnSuccess, [ErrorHandler callbackOnFailure])
: request = new XMLHttpRequest(),
_callbackOnSuccess = callbackOnSuccess,
_callbackOnFailure = callbackOnFailure {
request.open("GET", url, async : true);
request.on.loadEnd.add((XMLHttpRequestProgressEvent e) => onLoadEnd(e));
}
void send() {
request.send();
}
void onLoadEnd(XMLHttpRequestProgressEvent event) {
if (request.readyState == 4 && request.status == 200) {
_callbackOnSuccess(request.responseText);
} else if (_callbackOnFailure != null) {
_callbackOnFailure(request.statusText);
}
}
}

MVC rest API status code 500 with JSON return

I am developing a rest API on MVC3.
Whenever there is a problem with validation, I want to throw 500 + json that describes the error (the json can be the list of unvalidated fields).
The problem is that the json returns inside html that holds the entire HttpExeption (Server Error in '/' Application.)
If I put filterContext.ExceptionHandled = true; the message goes out clean, but the client can't see the 500 error on his side.
This case: https://stackoverflow.com/a/4707762/936651 actually the html and gives clean json to the client, but also removes the 500 error.
You could set the status code to 500 inside the custom error handler you have seen here:
filterContext.RequestContext.HttpContext.Response.StatusCode = 500;
and on the client:
$.ajax({
url: '/home/foo',
success: function (result) {
alert('success');
},
error: function (jqXHR, textStatus, errorThrown) {
var json = $.parseJSON(jqXHR.responseText);
// TODO: do something with the json that was returned from the server
}
});

ASP.NET MVC ajax chat

I built an ajax chat in one of my mvc website. everything is working fine. I am using polling. At certain interval i am using $.post to get the messages from the db. But there is a problem. The message retrieved using $.post keeps on repeating. here is my javascript code and controller method.
var t;
function GetMessages() {
var LastMsgRec = $("#hdnLastMsgRec").val();
var RoomId = $("#hdnRoomId").val();
//Get all the messages associated with this roomId
$.post("/Chat/GetMessages", { roomId: RoomId, lastRecMsg: LastMsgRec }, function(Data) {
if (Data.Messages.length != 0) {
$("#messagesCont").append(Data.Messages);
if (Data.newUser.length != 0)
$("#usersUl").append(Data.newUser);
$("#messagesCont").attr({ scrollTop: $("#messagesCont").attr("scrollHeight") - $('#messagesCont').height() });
$("#userListCont").attr({ scrollTop: $("#userListCont").attr("scrollHeight") - $('#userListCont').height() });
}
else {
}
$("#hdnLastMsgRec").val(Data.LastMsgRec);
}, "json");
t = setTimeout("GetMessages()", 3000);
}
and here is my controller method to get the data:
public JsonResult GetMessages(int roomId,DateTime lastRecMsg)
{
StringBuilder messagesSb = new StringBuilder();
StringBuilder newUserSb = new StringBuilder();
List<Message> msgs = (dc.Messages).Where(m => m.RoomID == roomId && m.TimeStamp > lastRecMsg).ToList();
if (msgs.Count == 0)
{
return Json(new { Messages = "", LastMsgRec = System.DateTime.Now.ToString() });
}
foreach (Message item in msgs)
{
messagesSb.Append(string.Format(messageTemplate,item.User.Username,item.Text));
if (item.Text == "Just logged in!")
newUserSb.Append(string.Format(newUserTemplate,item.User.Username));
}
return Json(new {Messages = messagesSb.ToString(),LastMsgRec = System.DateTime.Now.ToString(),newUser = newUserSb.ToString().Length == 0 ?"":newUserSb.ToString()});
}
Everything is working absloutely perfect. But i some messages getting repeated. The first time page loads i am retrieving the data and call GetMessages() function. I am loading the value of field hdnLastMsgRec the first time page loads and after the value for this field are set by the javascript.
I think the message keeps on repeating because of asynchronous calls. I don't know, may be you guys can help me solve this.
or you can suggest better way to implement this.
Kaivalya is correct about the caching, but I'd also suggest that your design could and should be altered just a tad.
I made a very similar app recently, and what I found was that my design was greatly enhanced by letting the controllers work with the fairly standard PRG pattern (post-redirect-get). Why enhanced? well, because POST methods are built to add stuff to an app, GET methods are supposed to be used to get information without side effects. Your polling should be just getting new messages w/o side effects.
So rather than your $.post call expecting data and handling the callback, what I'd recommend is having your controller expose a method for creating new chat messages via POST and then another method that get the last X chat messages, or the messages since a certain timestamp or whatever.
The javascript callback from the post action, then can update some variables (e.g. the last message id, timestamp of the last message, or even the whole URL of the next message based on the info contained in a redirect, whatever).
The $.post would fire only in response to user input (e..g type in a box, hit 'send') Then, you have (separately) a $.get call from jquery that's set up to poll like you said, and all it does is fetch the latest chat messages and it's callback updates the chat UI with them.
I got my answer here: ASP.NET AJAX CHAT
The names below i am referring to are from above link.
i think the actual problem was with the timestamp thing and asynchronous behaviour of $.post. after calling "GetMessages()" method, even if the previous request to retrive chat message was not complete anathor call to same method used to fire due to setting timeout for "GetMessages()" method outside the $.post method. In my question you can see that timeout for "GetMessages()" method is set outside the $.post method. Now i set the timeout for "GetMessages()" method inside the $.post method. so that next call to "GetMessages()" only occur after 3 seconds of completion of current $.post method. I have posted the code below.
var t;
function GetMessages() {
var LastMsgRec = $("#hdnLastMsgRec").val();
var RoomId = $("#hdnRoomId").val();
//Get all the messages associated with this roomId
$.post("/Chat/GetMessages", { roomId: RoomId, lastRecMsg: LastMsgRec }, function(Data) {
if (Data.LastMsgRec.length != 0)
$("#hdnLastMsgRec").val(Data.LastMsgRec);
if (Data.Messages.length != 0) {
$("#messagesCont").append(Data.Messages);
if (Data.newUser.length != 0)
$("#usersUl").append(Data.newUser);
$("#messagesCont").attr({ scrollTop: $("#messagesCont").attr("scrollHeight") - $('#messagesCont').height() });
$("#userListCont").attr({ scrollTop: $("#userListCont").attr("scrollHeight") - $('#userListCont').height() });
}
else {
}
t = setTimeout("GetMessages()", 3000);
}, "json");
}
I addition to that i also changed few things. As suggested by ignatandrei i placed $("#hdnLastMsgRec").val(Data.LastMsgRec); immediately after function(Data) {.
and also
as said by MikeSW i changed the data retrieval process. Previously i was extracting data on the basis of timespan(retrieve all the data associated with
this room id that has greater timespan than last data retrieved message timespan) but now i keep track of the messageid. Now i retrieve only those data that
has message id greater than last retrieved message id.
and guess what no repeataion and perfectly working chat application so far on my intranet.
I still got to see it's performance when deployed on internet.
i think it solved my problem.
i will still test the system and let u guys know if there is any problem.
By default $.post() caches the results
You can either call $.ajaxSetup ({ cache: false}); before JS GetMessages function call to ensure caching is disabled or change the $.post to $.ajax and set cache attribute to false. In the end $.post() is a short cut to this:
$.ajax({
type: 'POST',
url: url,
data: data,
success: success
dataType: dataType
});

Resources