I'm trying to get a custom error page to display from a MVC4 Mobile Application but keep just getting the "Error Loading Page" yellow message being displayed instead of my custom page.
I have tried using the HandleErrorAttribute as below on Actions and Controllers with no success
[HandleError(ExceptionType = typeof(SqlException), View = "DatabaseError")]
I have also tried overriding the OnException method of my base controller but this also doesn't appear to have any effect.
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
base.OnException(filterContext);
Logger.LogException(filterContext.Exception);
if (filterContext.Exception is SqlException)
{
filterContext.Result = new ViewResult { ViewName = "DatabaseError" };
}
if (filterContext.Exception is SomeOtherException)
{
filterContext.Result = new ViewResult { ViewName = "Error" };
}
if (filterContext.HttpContext.IsCustomErrorEnabled)
{
filterContext.ExceptionHandled = true;
filterContext.Result.ExecuteResult(this.ControllerContext);
}
}
If I try these methods on a non jQueryMobile MVC4 application they work as expected, just not in my mobile application!
Anyone have any insight as to why and how to make this work??
Ok so by disabling Ajax the appropriate error pages now get displayed!
In my _layout.cshtml page I added the following javascript:
$.mobile.ajaxEnabled = false;
You probably need to check in your filter if the request is via AJAX and return a JsonResult instead of a ViewResult, something like:
public class TypeSwitchingHandleErrorAttribute : HandleErrorAttribute
{
private static readonly string[] AJAX_ACCEPT_TYPES = new[] { "application/json", "application/javascript", "application/xml" };
private bool IsAjax(ExceptionContext filterContext)
{
return filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest"
||
filterContext.HttpContext.Request.AcceptTypes.ContainsAny(AJAX_ACCEPT_TYPES);
}
private void setResult(ExceptionContext filterContext, object content)
{
if( IsAjax(filterContext) )
{
filterContext.Result = new JsonResult { Data = content, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
} else
{
filterContext.Result = new ViewResult { ViewName = (string)content };
}
}
public override void OnException(ExceptionContext filterContext)
{
// your code...then where you set the result...
setResult(filterContext, "DatabaseError etc");
}
}
Then you'd have to interpret the ajax response appropriately on client-side. You could also send different content if it's an ajax request, like a standard {success: t/f, message: Exception.Message } object, and set the response status codes appropriately as well.
Related
I am implementing an authorization mechanizm for my MVC application via Custom Action Filters.
I have provided the following Custom Action Filter for authorization:
[AttributeUsageAttribute(AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class AuthorizationFilterAttribute : ActionFilterAttribute
{
public AuthorizationEntity Entity { get; set; }
public AuthorizationPermission Permission { get; set; }
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
AuthorizationResult result = null;
// Base actions (Authentication first)
base.OnActionExecuting(filterContext);
BaseController controller = filterContext.Controller as BaseController;
if (controller != null)
{ // Base actions (Authorizatioın next)
User usr = controller.currentUser;
AuthorizationResult ar = AuthorizationManager.GetAuthorizationResult(this.Entity, this.Permission, usr.UserId, usr.RoleId);
if (!ar.IsAuthorized)
{
throw new UnauthorizedAccessException(ar.Description);
}
// Authorized, continue
return;
}
}
}
And in my Base Controller class I am handling UnauthorizedAccessException type Exceptions and redirect them to a warning page via the following code
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.Exception is UnauthorizedAccessException)
{
if (!filterContext.HttpContext.Request.IsAjaxRequest())
{
Exception ex = filterContext.Exception;
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult()
{
ViewName = "UnauthorizedAccess"
};
}
else
{
throw filterContext.Exception;
}
}
}
This mechanism is OK for actions which return ActionResult. But I also have some AJAX calls, which I don't want to redirect to a warning page but would ilke to display a warning pop-up instead. Thi is why I have checked if the request is an Ajax call is not.
I am using the following code to make Ajax calls:
$.ajax({
type: "POST",
url: "AjaxPostMethodName",
dataType: "json",
data:
{
postval: [some value here]
},
success: function (msg) {
// Do some good actions here
},
error: function (x, t, m, b) {
// Display error
alert(m);
}
})
which goes to the following method on the Controller
public JsonResult AjaxPostMethodName(string postval)
{
try
{
// Some cool stuff here
return Json(null);
}
catch (Exception ex)
{
Response.StatusCode = UNAUTHORIZED_ACCESS_HTTP_STATUS_CODE;
return Json(ex.Message);
}
}
But when I fail the authorization check it directly shows the "Internal Server Error" message instead of falling to the catch block of AjaxPostMethodName method and displaying the proper message.
How can I make such code display filterContext.Exception instead of static "Internal Server Error" message?
Regards.
I finally found the answer to my solution in another Stack Overflow post (Can I return custom error from JsonResult to jQuery ajax error method?). I should use JsonExceptionFilterAttribute as follows:
public class JsonExceptionFilterAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 500;
filterContext.ExceptionHandled = true;
string msg = filterContext.Exception.Message;
if (filterContext.Exception.GetType() == Type.GetType("System.UnauthorizedAccessException"))
{
msg = "Unauthorized access";
}
filterContext.Result = new JsonResult
{
Data = new
{
errorMessage = msg
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
}
}
Your OnException method will get called when there is Unhandled exception in your code. And in your ajax method AjaxPostMethodName you have put your code in try catch blcok. So any exception in this method will not go to your OnException method.
I've just checked the Response.StatusCode behavior and for me it works.
Index.cshtml
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<script type="text/javascript">
$(document).ready(function () {
alert('doc ready');
$.ajax({
type: "POST",
url: '#Url.Action("AjaxPostMethodName")',
dataType: "json",
data:
{
test: '10'
},
success: function (msg) {
// Do some good actions here
alert('success');
alert(msg);
},
error: function (x, t, m, b) {
// Display error
alert('error');
}
});
});
</script>
HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public JsonResult AjaxPostMethodName(string postval)
{
Response.StatusCode = 401;
return Json("test");
}
}
}
When I set Response.StatusCode to 200 it calls success, when 401 it calls error.
Please verify whether other parts of your code don't interfere with it somehow.
You could try also following workaround - if AjaxPostMethodName throws exception returned JSON has a flag isValid and a message errorMessage, so in your ajax success method you can just check whether isValid is okay and handle error.
I search for a generic way to display thrown exceptions without redirecting to an error page but displaying it in the same view. I tried these below:
1) I firstly tried to handle them by adding a custom filter in global.asax and overriding public override void OnException(ExceptionContext filterContext) in my Attribute class but in that way, I couldn't fill filterContext.Result in the way I want since the old model of the view is not reachable so I could only redirect to an error page but that's not what I want.
2) Then I tried to catch the exceptions on my BaseController(All of my controllers inherits from it). I again override public override void OnException(ExceptionContext filterContext) in my controller and put exception details etc. in ViewBag and redirected the page to the same view by filterContext.HttpContext.Response.Redirect(filterContext.RequestContext.HttpContext.Request.Path ); but ViewBag contents are lost in the redirected page so I can't think any other way?
How can I achieve that? Code Sample that I wrote in my BaseController is below:
protected override void OnException(ExceptionContext filterContext) {
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
//filterContext.Result = new ViewResult
//{
// ViewName = actionName,
// ViewData = new ViewDataDictionary<??>(??),
// TempData = filterContext.Controller.TempData,
//};
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
ModelState.AddModelError("Error", filterContext.Exception.Message);
ViewBag.das = "dasd";
filterContext.HttpContext.Response.Redirect(filterContext.RequestContext.HttpContext.Request.Path);
}
Maybe you could set a property in your BaseController class to have the name of the view that you want to use, setting that in whatever controller action handles the request. Then in OnException() you could have a method, that redirects to a controller action, that just returns a View that corresponds to the view name? Each controller action would have to set a default view name before it does anything else because only it knows what view it will call if any, and what view it likely was invoked by.
You'd need some sort of BaseController action that returns the new View.
The route(s) may or many not need configuration to have some sort of optional parameter(s) that you could set to be what error information you want to send to your view. For example, in the default route:
routes.MapRoute(RouteNames.Default,
"{controller}/{action}/{id}",
new {controller = "Home", action = "Index", id = "", errorInfo = UrlParameter.Optional}
BaseController:
protected ActionResult ErrorHandler()
{
ViewBag.das = (string)filterContext.RouteData.Values["errorInfo"];
return View(ViewName);
}
protected string ViewName { get; set; }
protected void GoToErrorView(ExceptionContext context, string exceptionData)
{
var actionName = "ErrorHandler";
var newVals = new RouteValueDictionary();
newVals.Add("errorInfo", exceptionData);
this.RedirectToAction(actionName, newVals);
}
In BaseController.OnException():
// ...
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
ModelState.AddModelError("Error", filterContext.Exception.Message);
// anything else you need to do to prepare what you want to display
string exceptionData = SomeSortOfDataYouWantToPassIntoTheView;
this.GoToErrorView(filterContext, exceptionData);
}
In the specific controllers that inherit from BaseController that are returning an ActionResult specifically a ViewResult:
[HttpGet]
public ActionResult Index()
{
ViewName = <set whatever view name you want to here>
// code here, including preparing the Model
// ...
var model = new MyViewModel();
model.SomethingIWantToGiveTheView = someDataThatItNeeds;
// ...
return View(<model name>, model);
}
I found the solution a while ago and add the solution so that it may help the others. I use TempData and _Layout to display errors:
public class ErrorHandlerAttribute : HandleErrorAttribute
{
private ILog _logger;
public ErrorHandlerAttribute()
{
_logger = Log4NetManager.GetLogger("MyLogger");
}
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled)
{
return;
}
if (!ExceptionType.IsInstanceOfType(filterContext.Exception))
{
return;
}
// if the request is AJAX return JSON else view.
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;
}
// log the error using log4net.
_logger.Error(filterContext.Exception.Message, filterContext.Exception);
filterContext.ExceptionHandled = true;
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] != "XMLHttpRequest")
{
if (filterContext.Controller.TempData["AppError"] != null)
{
//If there is a loop it will break here.
filterContext.Controller.TempData["AppError"] = filterContext.Exception.Message;
filterContext.HttpContext.Response.Redirect("/");
}
else
{
int httpCode = new HttpException(null, filterContext.Exception).GetHttpCode();
switch (httpCode)
{
case 401:
filterContext.Controller.TempData["AppError"] = "Not Authorized";
filterContext.HttpContext.Response.Redirect("/");
break;
case 404:
filterContext.Controller.TempData["AppError"] = "Not Found";
filterContext.HttpContext.Response.Redirect("/");
break;
default:
filterContext.Controller.TempData["AppError"] = filterContext.Exception.Message;
//Redirect to the same page again(If error occurs again, it will break above)
filterContext.HttpContext.Response.Redirect(filterContext.RequestContext.HttpContext.Request.RawUrl);
break;
}
}
}
}
}
And in Global.asax:
protected void Application_Error(object sender, EventArgs e)
{
var httpContext = ((MvcApplication)sender).Context;
var ex = Server.GetLastError();
httpContext.ClearError();
httpContext.Response.Clear();
httpContext.Response.StatusCode = ex is HttpException ? ((HttpException)ex).GetHttpCode() : 500;
httpContext.Response.TrySkipIisCustomErrors = true;
var routeData = new RouteData();
routeData.Values["controller"] = "ControllerName";
routeData.Values["action"] = "ActionName";
routeData.Values["error"] = "404"; //Handle this url paramater in your action
((IController)new AccountController()).Execute(new RequestContext(new HttpContextWrapper(httpContext), routeData));
}
I am trying to write an FilterAttribute, IExceptionFilter global error handling attribute which should serve up a custom error view. I override public void OnException(ExceptionContext context) and do
context.HttpContext.Response.Clear();
context.HttpContext.Response.TrySkipIisCustomErrors = true;
context.HttpContext.Response.StatusCode = 403;
context.ExceptionHandled = true;
context.Result = new ViewResult { ... not sure what to put here... }
which is where I am stuck.
Everything I have tried so far causes me to see internal server error pages. I suspect I want something like
context.Result = new ViewResult
{
ViewName = "~/Errors/ReadOnlyMode.cshtml",
}
However that didn't seem to work (internal server errors...).
This should work
context.Result = new ViewResult
{
ViewName = "~/Views/Errors/ReadOnlyMode.cshtml",
}
context.Result = new RedirectToRouteResult(
new RouteValueDictionary
{
{ "Controller", "Errors" },
{ "Action", "ReadOnlyMode" }
});
This works fine for me.
I'm making an ajax call using jquery to an asp.net mvc controller action:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult GetWeek(string startDay)
{
var daysOfWeek = CompanyUtility.GetWeek(User.Company.Id, startDay);
return Json(daysOfWeek);
}
When session times out, this call will fail, as the User object is stored in session. I created a custom authorize attribute in order to check if session was lost and redirect to the login page. This works fine for page requests, however it doesn't work for ajax requests, as you can't redirect from an ajax request:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorizeUserAttribute : AuthorizeAttribute
{
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (!httpContext.Request.IsAjaxRequest())
{//validate http request.
if (!httpContext.Request.IsAuthenticated
|| httpContext.Session["User"] == null)
{
FormsAuthentication.SignOut();
httpContext.Response.Redirect("~/?returnurl=" + httpContext.Request.Url.ToString());
return false;
}
}
return true;
}
}
I read on another thread that when the user isn't authenticated and you make an ajax request, you should set the status code to 401 (unauthorized) and then check for that in js and redirect them to the login page. However, I can't get this working:
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (Request.IsAjaxRequest() && (!Request.IsAuthenticated || User == null))
{
filterContext.RequestContext.HttpContext.Response.StatusCode = 401;
}
else
{
base.OnActionExecuting(filterContext);
}
}
Basically, it'll set it to 401, but then it'll continue into the controller action and throw an object ref not set to an instance of an object error, which then returns error 500 back to the client-side js. If I change my custom Authorize attribute to validate ajax requests as well and return false for those that aren't authenticated, that makes the ajax request return my login page, which obviously doesn't work.
How do I get this working?
You could write a custom [Authorize] attribute which would return JSON instead of throwing a 401 exception in case of unauthorized access which would allow client scripts to handle the scenario gracefully:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
Data = new
{
// put whatever data you want which will be sent
// to the client
message = "sorry, but you were logged out"
},
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
}
else
{
base.HandleUnauthorizedRequest(filterContext);
}
}
}
then decorate your controller/actions with it and on the client:
$.get('#Url.Action("SomeAction")', function (result) {
if (result.message) {
alert(result.message);
} else {
// do whatever you were doing before with the results
}
});
I wouldn't change JsonRequestBehavior to AllowGet. Instead I suggest:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class MyAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);
OnAuthorizationHelp(filterContext);
}
internal void OnAuthorizationHelp(AuthorizationContext filterContext)
{
if (filterContext.Result is HttpUnauthorizedResult)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.HttpContext.Response.End();
}
}
}
}
and add global js ajax errors handler:
$(document).ajaxError(function (xhr, props) {
if (props.status === 401) {
location.reload();
}
}
Even though this is well past answered, I think this is the shortest and sweetest answer if you are using .NET 4.5. Little property called SuppressFormsAuthenticationRedirect which was added. Set to true and it will not perform the 302 Redirect to login page.
http://msdn.microsoft.com/en-us/library/system.web.httpresponse.suppressformsauthenticationredirect.aspx
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AjaxAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// returns a 401 already
base.HandleUnauthorizedRequest(filterContext);
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
// we simply have to tell mvc not to redirect to login page
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
}
}
}
Assuming you plan on handling the ajax requests fail/error callback, in which you will get a 401 Unauthorized.
On Master page add this jquery script ------------
<script type="text/javascript">
$.ajaxSetup({
statusCode: {
403: function () {
window.location.reload();
}
}
});
OR
$.ajaxSetup({
error: function (x, e) {
if (x.status == 403) {
window.location.reload();
}
}
});
</script>
Add a cs file named with TraceFilter in your project and write a seald class TraceFilterAttribute inheriting to ActionFilterAttribute.
Add TraceFilterAttribute class in FilterConfig.cs available in App_Start folder of your project by writing below line.
filters.Add(new TraceFilterAttribute());
Override method OnActionExecuting() in TraceFilterAttribute class. This will automatically check session and if finds session null then calls script available in master page and from their you can go to your choice page.
[AttributeUsage(AttributeTargets.All)]
public sealed class TraceFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if (filterContext != null)
{
HttpSessionStateBase objHttpSessionStateBase = filterContext.HttpContext.Session;
var userSession = objHttpSessionStateBase["etenetID"];
if (((userSession == null) && (!objHttpSessionStateBase.IsNewSession)) || (objHttpSessionStateBase.IsNewSession))
{
objHttpSessionStateBase.RemoveAll();
objHttpSessionStateBase.Clear();
objHttpSessionStateBase.Abandon();
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.HttpContext.Response.StatusCode = 403;
filterContext.Result = new JsonResult { Data = "LogOut" };
}
else
{
filterContext.Result = new RedirectResult("~/Admin/GoToLogin");
}
}
}
}
}
I was having a similar issue and found this
Instead of returning any JSON, just before the response is sent back, force ASP.NET to return a 401 code. In Global.asax:
protected void Application_EndRequest()
{
var context = new HttpContextWrapper(Context);
if (context.Request.IsAjaxRequest() && context.Response.StatusCode == 302)
{
Context.Response.Clear();
Context.Response.Write("**custom error message**");
Context.Response.StatusCode = 401;
}
}
Then you can let the client deal with it in JavaScript/jQuery or whatever you are using
here is how I handle this in so simple way in my custom authorization , I check if session is out and handle this as un-authorized with a boolean to check if it is really authenticated but not authorized (to redirect to un-authorized page) or it is not authenticated due to session time out ( redirect to Login)
private bool ispha_LoggedIn = false;
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
ispha_LoggedIn = false;
var session = httpContext.Session;
bool authorize = false;
if (httpContext.Session["authenticationInfo"] == null)
{
return authorize;
}
using (OrchtechHR_MVCEntities db = new OrchtechHR_MVCEntities())
{
UserAuthenticationController UM = new UserAuthenticationController();
foreach (var roles in userAssignedRoles)
{
authorize = UM.IsUserInRole(httpContext.User.Identity.Name, roles);
if (authorize)
{
return authorize;
}
}
}
ispha_LoggedIn = true;
return authorize;
}
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
if (ispha_LoggedIn==false)
{
filterContext.Result = new RedirectResult("~/UserAuthentication/LogIn");
}
else
{
filterContext.Result = new RedirectResult("~/Dashboard/UnAuthorized");
}
}
Hope if this guides someone and please if there're comments its appreciated to know them though.
You might want to try to throw HttpException and catch it in your javascript.
throw new HttpException(401, "Auth Failed")
on ajax call if session expired return something like this
<script>
$(function(){
location.reload();
});
</script>
haha...
public JsonResult Menu() { // Exception }
I need application not to redirect user to the 404 page, but return special JSON result like { "result":1 }.
I wonder, is there any another solution, not try-catching.
You can implement your own FilterAttribute similar to the HandleErrorAttribute.
The HandleErrorAttribute normally does a redirect when an error occurs, but you could implement a similar attribute that returns a JsonResult. Something like the following will do:
public class CustomHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
filterContext.Result = new JsonResult
{
Data = new { result = 1 },
JsonRequestBehavior = JsonRequestBehavior.AllowGet
};
filterContext.ExceptionHandled = true;
}
}
And then
[CustomHandleError]
public JsonResult Menu()
{
throw new Exception();
}
I would recommend that you download the MVC source code from CodePlex and inspect the current implementation of HandleErrorAttribute. It is a lot more subtle than my crude implementation above and you may want some of its functionality.