I've been struggling to get around this problem for quite a while now but I cannot seem to find a solution that works for me.
I handle all errors by overriding OnException method in my BaseController class, which all others controllers inherit.
protected override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
var rd = new RouteData
{
Values = { ["controller"] = "Error", ["action"] = "Index" },
DataTokens = { }
};
var error = $#"Error: {filterContext.Exception.Message} in {filterContext.HttpContext.Request.FilePath}
Details:
{filterContext.Exception.StackTrace}";
_logger = LogManager.GetLogger(GetType());
_logger.Error(error + Environment.NewLine + "Temp Id: " + AppSession.TempId);
IController c = new ErrorController();
c.Execute(new RequestContext(new HttpContextWrapper(System.Web.HttpContext.Current), rd));
}
My error controller is pretty simple:
public ActionResult Index()
{
ViewBag.Error = "Oops.. Something went wrong";
return View("Error");
}
It works, Error page shows up, but it always loads up inside the partial view container, the partial view that raised the error. Instead, I want to do a proper redirect to just error page alone.
I've tried using and handle errors that way but it behaves in exact same manner. I've also tried handling errors in Global.asax Application_Error method, which I knew wouldn't make any difference but I wanted
to try it anyways..
My guess is because the partial view is loaded via $.get call it somehow wraps the response in the same div/container the partial view was supposed to load.
Any help would be greatly appreciated. Should you need more information, please let me know.
I've also tried looking up on SO for similar scenarios but no post, that i've found, has a good solution...
Thanks in advance.
What you should be doing is, If the error happens in an ajax call, you should be sending a json response with a property which indicates which url to redirect to. If it is not an ajax request, you can send the normal redirectresult.
Something like this
protected override void OnException(ExceptionContext filterContext)
{
//your existing code to log errors here
filterContext.ExceptionHandled = true;
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest")
{
var targetUrl = UrlHelper.GenerateUrl("Default", "Index", "Error",
new RouteValueDictionary(), RouteTable.Routes, Request.RequestContext, false);
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new { Error = true, Message = filterContext.Exception.Message,
RedirectUrl= targetUrl }
};
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.ExceptionHandled = true;
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{{"controller", "Error"}, {"action", "Index"}});
}
}
Now you can listen the .ajaxError() event which will be fired anytime an ajax request completes with an error. Get the RedirectUrl value and redirect as needed. You may also consider showing a meaningful message to user (Even in a partial view from the modal dialog) so user won't be confused by the blind redirect !
$(function () {
$(document).ajaxError(function (event, request, settings) {
console.log('ajax request', request.responseText);
var d = JSON.parse(request.responseText);
alert("Ajax error:"+d.Message);
window.location.href = d.RedirectUrl;
});
});
Related
In my asp.net mvc application, I would like to show the user the error message that was used to throw an exception. The exception occurs in an ajax request. I have tried this:
In Global.asax.cs file, I have global application error handler:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = System.Web.HttpContext.Current.Server.GetLastError();
// do something so that client side gets the value exception.Message
// I have tried these, but no success
HttpContext.Current.Response.StatusDescription = exception.Message;
HttpContext.Current.Response.StatusCode = 1000; // custom status code
}
In javascript, I have global ajaxError handler:
$(document).ajaxError(function (xhr, props) {
// show the message in the exception thrown on the server side
});
I tried to get the exception message in JavaScript using props.statusText, props.responseText, but none of them had the exception message.
My question: What can I do in the Application_Error method so that I can get the message contained in exception.Message to the global ajaxError function on the client side? Please note that I can easily handle exception that occurs in any specific Action where an ajax request hits, but I would like to make a global exception handler, that will allow me to just throw an exception with a message from any part of my application, and the exception message gets shown to the user on the client side.
I have tried this, but this doesn't solve my problem, because I want to use jQuery global ajaxError, instead of handling error in just a specific ajax request. Can this be modified to achieve what I want?
You can create a basecontroller and override the OnException method
public class BaseController : Controller
{
protected override void OnException(ExceptionContext filterContext)
{
//your existing code to log errors here
filterContext.ExceptionHandled = true;
if (filterContext.HttpContext.Request.Headers["X-Requested-With"]
== "XMLHttpRequest")
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
Error = true,
Message = filterContext.Exception.Message
}
};
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.ExceptionHandled = true;
}
else
{
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{{"controller", "Error"}, {"action", "Index"}});
}
}
}
and have all your controllers inherit from this
public class HomeController : BaseController
{
}
So any time an exception happens in your controllers, this OnException method will be executed and if it is an ajax request, It returns a json response with the below structure
{
Error : true,
Message : "The message from the exception caught"
}
and now in your javascript, wire up the global ajaxError event and you can read the response coming from server and parse it to a js object and then read the Message property
$(document).ready(function() {
$(document).ajaxError(function (event, request, settings) {
var d = JSON.parse(request.responseText);
alert("Ajax error:"+d.Message);
});
});
If the user is on a page for a long time and the session ends, if they proceed to make an AJAX call after the session is already expired.. instead of receiving the JSON object, it instead receives the HTML of the login page.
Ideally I'm trying to make it so that it will redirect to a log in page.
Is there any way i can detect this?
I already have an ActionFilterAttribute that works for non-AJAX calls like so:
public class VerifySessionAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var userId = filterContext.HttpContext.Session["UserId"];
var userName = filterContext.HttpContext.Session["UserName"];
if (userId == null || userName == null)
{
filterContext.Result = new RedirectResult(string.Format("/Account/Login"));
return;
}
base.OnActionExecuting(filterContext);
}
}
But that doesn't get hit for the scenario above during AJAX calls.
I've also tried an Interceptor.. something like this:
app.factory('httpAuthInterceptor', function ($q) {
return {
'responseError': function (response) {
// NOTE: detect error because of unauthenticated user
if ([401, 403].indexOf(response.status) >= 0) {
// redirecting to login page
//$state.go('home');
$window.location.href = '/Account/Login';
return response;
} else {
return $q.reject(rejection);
}
}
};
})
app.config(function ($httpProvider) {
$httpProvider.interceptors.push('httpAuthInterceptor');
});
But in the same scenario it doesn't seem to hit there as well during the expired session / AJAX call
Is there anything I can do to detect this? When the session is expired I just want to redirect to the login page.. Thanks for any help
EDIT: here's how I make my calls
app.factory('HeadlinesFactory', ['$http', function ($http) {
var HeadlinesFactory = {};
HeadlinesFactory.getShowsForClient = function (clientId) {
return $http({
method: 'POST',
url: '/Show/GetShowsForClient',
data: { clientId: JSON.stringify(clientId) }
});
};
//etc
EDIT2: how all my controllers look like. Except my Account Controller where I put the VerifySession in front of everything except the Login page to prevent loop redirects:
[Authorize]
[CustomFilters.VerifySession]
public class ShowController : Controller
{ ... }
Ajax requests will not process redirect requests for security reasons. In addition, since you are returning a redirect result, a 401/403 status code is not thrown but rather a 302 is returned.
What you could do is expand your filter to conditionalize logic based on whether or not the request is an ajax request. In addition, based on your comments, it seems like creating a new Authorize attribute instead would be the right way to go since that way you can simply replace the default Authorize attribute with your own logic.
public class VerifySessionAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect =
true;
}
else
{
filterContext.Result = new RedirectResult(string.Format("/Account/Login"));
return;
}
}
}
}
This would allow your Angular interceptor to pick up the request and handle it appropriately.
Since IsAjaxRequest looks explicitly for the "X-Requested-With": "XMLHttpRequest" and AngularJS no longer provides that header with Ajax requests, you can add a configuration to the $httpProvider to always include the header.
app.config(['$httpProvider', function ($httpProvider) {
$httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
}]);
This is the way I'm populating the attribute of my model:
this.model.set('questionAnswers', arrQuestions);
Now on submit, I check if model is valid:
if (this.model.isValid()) {
this.model.save(null, { success: this.gradingQuestionsSuccess, error: this.gradingQuestionsFailed });
}
Validations works like this:
validate: function (attr, options) {
var error = null;
if (attr.questionAnswers.length < this.cntQues) {
this.trigger('empty:answers');
error = 'Please answer all the questions.';
}
return error;
}
And service call is:
url: function () {
var url = Application.getConfig("url") + "/";
url += Application.getConfig("v2path3") + "/account/submitGradingQuestions";
}
return url;
}
The model is valid and the values are set in it on Submit, but it's not sending it in the Request Payload.
Can anyone please help me understand why this is happening? Thanks in advance!
Backbone does not observe the changed attributes and sync on save(null) automatically. What you need is pass attributes to Model.save method that makes set under the hood here or here depending on wait option's property. So you just need the following:
var data = {questionAnswers: arrQuestions};
this.model.save(data, {
success: this.gradingQuestionsSuccess,
error: this.gradingQuestionsFailed
});
You also don't need invoke validation manually because it's get invoked in save method also.
I am new to MVC, so please ignore my mistakes. I want to call a JavaScript function from controller, I tried myself and searched to call JavaScript function but did not find any reasonable solution. Please help
Here is my code.
[AllowAnonymous]
public ActionResult Index()
{
LogisticQuote lq = new LogisticQuote();
if (Request.QueryString["token"] != null)
{
byte[] byteArray = Convert.FromBase64String(Request.QueryString["token"]);
string values = System.Text.Encoding.UTF8.GetString(byteArray);
EdgeMoveService serivice = new EdgeMoveService();
Edge.Move.Common.Status.ServiceStatus serviceStatus = serivice.GetLogisticQuote(values.Split('&')[1].Split('=')[1], String.Empty, new TenantId(values.Split('&')[0].Split('=')[1]), "", 1, 1, new SaveId(values.Split('&')[2].Split('=')[1]), out lq);
if (serviceStatus.IsOkay)
{
TimeSpan difference = (DateTime.Now.Subtract(lq.CreatedTimeStamp));
if (difference.TotalHours <= 24)
{
if (!lq.IsExpire)
{
return View("QuoteDetails", lq);
}
else
{
ViewBag.Message = "alertError('" + String.Empty + "');";
return View("ExpireLinkNotification", lq);
}
}
else
{
return View("ExpireLinkNotification", lq);
}
}
}
return View("Startup", lq);
}
This is not a good practice.
You should avoid messing up javascript in you controllers code, thats what MVC is built for. Separation of concerns.
What you can do?
Pass the message to be displayed in the ViewBag or ViewData.
Receive this message in the script tag at the view side (store it in a javascript variable).
Check if message is non-empty show it in alert.
View.cshtml
<script>
var msg='#ViewBag.Message';
if(msg && msg.length>0)
alert(msg);
</script>
You have two options to do that.
Option #1
Call the controller method from using jquery ajax call and then in the success method you can fire your js method.
Option #2
In the document.ready event of your cshtml page, you can do like this
$.document.ready(function(){
var msg = '#ViewBag.Message'
if(msg != undefined && msg !== "")
alert(msg);
});
I need a cross domain web api method to return valid jsonp to some javascript from C#. I can't seem to make this magic happen. I've looked around the web and can't find a start to end example that fits my needs and works... Fiddler shows that I'm returning valid json data but when I hit a breakpoint in F12 dev tools or firebug the result is a failure message.
Here is what I've currently got:
C#
/// <summary>
/// POST: /Instance/RefreshItem
/// </summary>
/// <param name="instanceId"></param>
/// <returns>Json</returns>
[HttpPost]
public System.Web.Mvc.JsonResult RefreshItem(int instanceId, Guid customerId)
{
try
{
var clientConnection = Manager.ValidateInstance(customerId, instanceId);
clientConnection.RefreshItem();
var result = new MethodResult()
{
Success = true,
Value = instanceId,
Message = "Item successfully refreshed."
};
return new System.Web.Mvc.JsonResult() { Data = result };
}
catch (Exception ex)
{
Manager.LogException(_logger, ex, customerId, instanceId);
var result = new MethodResult()
{
Success = false,
Value = instanceId,
Message = ex.GetBaseException().Message
};
return new System.Web.Mvc.JsonResult() { Data = result };
}
}
JS
Example.RefreshItem = function ()
{
Example.SDK.JQuery.getSettings(
function (settings, userId, userLocaleId)
{
alert("Attempting to refresh item for instance " + settings.ConnectionId + "\r\nThis may take awhile.");
var url = settings.SystemUrl + "/Api/WebApiServices/ExampleAdmin/RefreshItem?customerId=" + settings.CustomerId + "&instanceId=" + settings.ConnectionId;
$.ajax({
url: url,
dataType: "jsonp",
jsonpCallback: 'RefreshItemCallback',
success: RefreshItemCallback
})
},
Example.SDK.JQuery.defaultErrorCallback
);
}
function RefreshItemCallback(data)
{
alert(data.d.Message);
}
I've also tried $.Post().Always() with the same results.
What am I doing wrong???
I think your problem is that you're instantiating a JsonResult instead of using the Json method.
Presumably the C# method you have is in a controller, so instead of
return new System.Web.Mvc.JsonResult() { Data = result };
do:
return Json(result);
This method probably sets some of the other properties of the JsonResult that, when not set, will not be properly received by the client.
See how Microsoft only shows you how to create a JsonResult via the Json method on MSDN
Note that the same is probably true with methods like View, Content, and File.
Fight all week unable to find an answer until you ask the question somewhere... Within 30 minutes of asking I found this: http://bob.ippoli.to/archives/2005/12/05/remote-json-jsonp/ which was exactly what I needed.
Thanks to all who posted.