Handle Session Timeout in .Net MVC Razor - asp.net-mvc

I am working on a website which is API based, client side is being developed in .Net MVC. For exception handling, I am using
public void Application_Error(object sender, EventArgs e)
{
string action = "Index";
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
if (httpException != null)
{
switch (httpException.GetHttpCode())
{
case 404:
// page not found
action = "Error404";
break;
default:
action = "Index";
break;
}
// clear error on server
Server.ClearError();
}
Response.Redirect(String.Format("/error/{0}", action));
}
so for any exception thrown by try catch from Controller, the page redirects to error page.
Now I want that when session is expired it should redirect to Login page, How can I do that?
Right now what is happening is, after session expires, when I try to access the session value, it throws exception "object reference not set to an instance of object." then it redirects to the default error page.

I don't think you're going to be able to do this from inside a generic exception handler because - as you said - missing session variables simply throw a NullReferenceException. Perform a null check on the session variable from your controller:
Public ActionResult MyAction ()
{
if (Session["myVariable"] == null)
{
RedirectToAction("SessionTimeOut", "Error");
}
...
}
If you have session variables that should always exist unless the session has expired, you could try overriding the OnActionExecuting method for your controller and performing your null check in there. To do this for multiple controllers, define a BaseController, override its OnActionExecuting method and then inherit this in your other controllers.

Related

Error propagation in controllers MVC

When I try to propagate an exception and pass it as parameter into my ErrorController, it is always null.
Controller
public ActionResult Test()
{
try
{
throw new Exception("ALGO");
//
return View();
}
catch (Exception ex)
{
return RedirectToAction("Error", "Error",
new
{
exception = ex,
controller = this.ControllerContext.RouteData.Values["controller"],
action = this.ControllerContext.RouteData.Values["action"]
});
}
}
ErrorController
public ActionResult Error(Exception exception, string controller, string action)
{
// exception is always null...
Response.StatusCode = 500;
ViewBag.exception = new HandleErrorInfo(exception, controller, action);
return View();
}
Any idea how to get the exception properly?
Is there a better approach for error handling?
I also tried this one but I got several errors because of parameteless constructor for handleerrorinfo
Whenever you use RedirectToAction, it performs an HTTP redirect. Any of the values you pass have to be primitive types, since they will be appended to the redirect URL. That means that you cannot pass an entire object, like you are trying to do with the exception. The easiest thing that you can do is to replace the RedirectToAction with
return Error(ex, this.ControllerContext.RouteData.Values["controller"], this.ControllerContext.RouteData.Values["action"]);
This approach will still call your Error method and display the View properly, but it will not change the URL like a redirect would. If you wanted to use this method, then you could try using javascript to change the URL.
Also, do you really want to display all of the error details to your end user? If you are just using this to display a plain error page without details then you could look into simply using the customErrors attribute in your web config to redirect to an error page. That way all that your end user knows is that some error occured.

Try to stay on the page when expected exception was thrown

I have CustomHandleErrorAttribute where i override OnException method like this:
public override void OnException(ExceptionContext filterContext) {
if (!filterContext.ExceptionHandled) {
if (filterContext.HttpContext.Request.Headers["X-Requested-With"] == "XMLHttpRequest") {
filterContext.Result = new JsonResult {
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new {
error = true,
message = filterContext.Exception.Message
}
};
}
else {
var controllerName = (string)filterContext.RouteData.Values["controller"];
var actionName = (string)filterContext.RouteData.Values["action"];
var model = new HandleErrorInfo(filterContext.Exception, controllerName, actionName);
filterContext.Result = new ViewResult {
ViewName = View,
MasterName = Master,
ViewData = new ViewDataDictionary<HandleErrorInfo>(model),
TempData = filterContext.Controller.TempData
};
}
}
}
I can get exceptions by AJAX request and while my Action is processing.
I have a problem, when exception thrown (for example on page /Customers/Customer/1), i have yellow screen with Server Error in '/' Application, but i would like to display my view and pass to ViewData information about exception handled, and then display in on this page (do not redirect to CustomErrorPage or anywhere else).
So:
1. If i have exception- just display exception info without form;
2. If i don't have exception- display form;
Is it possible, or thrown exception could not continue processing action and displaying same view?
Thx.
You get the yellow screen of death when you allow an exception to bubble all the way up to the level of IIS without being caught. This becomes a 500 Server Error and you will get the yellow screen or your 500 error page, if you've set one. There's nothing else that can be displayed because there's no way to recover from the error.
The only way to get the same view to load is to catch every exception and respond to it in some way. That might be generating an error message for the user on the view or something else, but it's up to you to catch the exception and respond, IIS will not do this for you.

How to access data after redirect

Again there are multiple articles which says how to access data after redirect. but doesn't serves my purpose.
I am having errorcontroller which is having index action method and error index view.
If there is any error in the application it will caught in Application_Error event.
inside Application_Error event I had logged the error and redirected to Error Index page like this -
protected new void Application_Error(object sender, EventArgs e)
{
Exception error = Server.GetLastError();
log.error(error.Message);
HttpContext.Current.Response.Redirect("~/Error/Index");
}
Now in the error index view, I would like to display the error message. What should I do in Application_Error event which can be access by Error Index view?
Updated : I don't want to use Session as session object may not be available in Application_Error event. this is dependent on when the error occurred.
Approach - 1
As per my knowledge you can use TempData to store the posted data. It is like a DataReader Class, once read, Data will be lost. So that stored data in TempData will become null.
var Value = TempData["keyName"] //Once read, data will be lost
So to persist the data even after the data is read you can Alive it like below
var Value = TempData["keyName"];
TempData.Keep(); //Data will not be lost for all Keys
TempData.Keep("keyName"); //Data will not be lost for this Key
TempData works in new Tabs/Windows also, like Session variable does.
You could use Session Variable also, Only major problem is that Session Variable are very heavy comparing with TempData. Finally you are able to keep the data across Controllers/Area also.
Approach - 2
This works for me. This is very easy and no need to consider any change in Web.Config or Register the Action Filter in Global.asax file.
ok. So, First I am creating a simple Action Filter. This will handle Ajax and Non Ajax requests.
public class MyCustomErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
var debugModeMsg = filterContext.HttpContext.IsDebuggingEnabled
? filterContext.Exception.Message +
"\n" +
filterContext.Exception.StackTrace
: "Your error message";
//This is the case when you need to handle Ajax requests
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
filterContext.Result = new JsonResult
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new
{
error = true,
message = debugModeMsg
}
};
}
//This is the case when you handle Non Ajax request
else
{
var routeData = new RouteData();
routeData.Values["controller"] = "Error";
routeData.Values["action"] = "Error";
routeData.DataTokens["area"] = "app";
routeData.Values["exception"] = debugModeMsg;
IController errorsController = new ErrorController();
var exception = HttpContext.Current.Server.GetLastError();
var httpException = exception as HttpException;
if (httpException != null)
{
Response.StatusCode = httpException.GetHttpCode();
switch (System.Web.HttpContext.Current.Response.StatusCode)
{
case 504:
routeData.Values["action"] = "Http404";
break;
}
}
var rc = new RequestContext
(
new HttpContextWrapper(HttpContext.Current),
routeData
);
errorsController.Execute(rc);
}
base.OnException(filterContext);
}
}
Now you can implement this Action Filter on Controller as well as on the Action only.Example:
I am going little off topic. I thought this is bit important to explain.
If you pay attention to the above highlighted part. I have specified the order of the Action Filter. This basically describes the order of execution of Action Filter. This is a situation when you have multiple Action Filters implemented over Controller/Action Method
This picture just indicates that let's say you have two Action Filters. OnActionExecution will start to execute on Priority and OnActionExecuted will start from bottom to Top. That means in case of OnActionExecuted Action Filter having highest order will execute first and in case of OnActionExecuting Action Filter having lowest order will execute first. Example below.
public class Filter1 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will start here - 1
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 5
base.OnActionExecuted(filterContext);
}
}
public class Filter2 : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//Execution will move here - 2
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//Execution will move here - 4
base.OnActionExecuted(filterContext);
}
}
[HandleError]
public class HomeController : Controller
{
[Filter1(Order = 1)]
[Filter2(Order = 2)]
public ActionResult Index()
{
//Execution will move here - 3
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}
}
You may already aware that there are different types of filters within MVC framework. They are listed below.
Authorization filters
Action filters
Response/Result filters
Exception filters
Within each filter, you can specify the Order property. This basically describes the order of execution of the Action Filters.
Use TempData for getting value.
Some feature about TempData
TempData is a dictionary object that is derived from TempDataDictionary class and stored in short lives session.
TempData is used to pass data from current request to subsequent request means incase of redirection.
It’s life is very short and lies only till the target view is fully loaded.
It’s required typecasting for complex data type and check for null values to avoid error.
It is used to store only one time messages like error messages, validation messages.

potentially dangerous request redirect to error page

I use request validation on asp.net mvc 2 on .NET 4, and whenever I post htmlcode to my controller action. that is good.
But I see now my yellow screen of death.
Instead of that I want to redirect the user to my custom error page for this.
What do I need to change in my web.config to redirect to
~/Home/InvalidInput
for example.
You want to add that info to the customErrors element in the web.config. This element is defined under the system.web tag.
<customErrors mode="RemoteOnly" defaultRedirect="/Home/InvalidInput " />
Also, you will need to define a route to handle this redirect since that is nothing more than a URL. Just specify an route and an action in your HomeController that will return the InvalidInput view when this route is hit.
Setup a custom base controller that HomeController (and whatever others you have) inherits from. Then you can setup exception handling in the base controller:
protected override void OnException(ExceptionContext filterContext)
{
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
base.OnException(filterContext);
return;
}
filterContext.ExceptionHandled = true;
if (filterContext.Exception is HttpException)
{
var statusCode = ((HttpException) filterContext.Exception).GetHttpCode();
Response.StatusCode = statusCode;
if (statusCode == (int) HttpStatusCode.NotFound)
{
filterContext.Result = View(ErrorController.Actions.NotFound);
}
else
{
filterContext.Result = View(ErrorController.Actions.InternalServerError);
}
}
else
{
Response.StatusCode = (int) HttpStatusCode.InternalServerError;
filterContext.Result = View(ErrorController.Actions.InternalServerError);
}
base.OnException(filterContext);
}
Of course you will need your own ErrorController and error views as well.
The benefit in this is that a) you have greater control over how exceptions are handled, b) you can set the appropriate HTTP status code, and c) you can unit test your error handler.
The defaultRedirect property of customErrors will work as well as a more basic approach, however you cannot unit test the behavior, and users are then exposed to your error routes rather than staying on the current URI.

Error Handling in ASP.NET MVC

How can I correctly handle exceptions thrown from controllers in ASP.NET MVC? The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
Using this web.config
<customErrors mode="On">
<error statusCode="401" redirect="/Errors/Http401" />
</customErrors>
with the following code
namespace MvcApplication1.Controllers
{
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
// Force a 401 exception for testing
throw new HttpException(401, "Unauthorized");
}
}
}
doesn't result in what I was hoping for. Instead I get the generic ASP.NET error page telling me to modify my web.config to see the actual error information. However, if instead of throwing an exception I return an invalid View, I get the /Shared/Views/Error.aspx page:
return View("DoesNotExist");
Throwing exceptions within a controller like I've done above seems to bypass all of the HandleError functionality, so what's the right way to create error pages and how do I play nice with the MVC infrastructure?
Controller.OnException(ExceptionContext context). Override it.
protected override void OnException(ExceptionContext filterContext)
{
// Bail if we can't do anything; app will crash.
if (filterContext == null)
return;
// since we're handling this, log to elmah
var ex = filterContext.Exception ?? new Exception("No further information exists.");
LogException(ex);
filterContext.ExceptionHandled = true;
var data = new ErrorPresentation
{
ErrorMessage = HttpUtility.HtmlEncode(ex.Message),
TheException = ex,
ShowMessage = !(filterContext.Exception == null),
ShowLink = false
};
filterContext.Result = View("ErrorPage", data);
}
Thanks to kazimanzurrashaid, here is what I wound up doing in Global.asax.cs:
protected void Application_Error()
{
Exception unhandledException = Server.GetLastError();
HttpException httpException = unhandledException as HttpException;
if (httpException == null)
{
Exception innerException = unhandledException.InnerException;
httpException = innerException as HttpException;
}
if (httpException != null)
{
int httpCode = httpException.GetHttpCode();
switch (httpCode)
{
case (int) HttpStatusCode.Unauthorized:
Response.Redirect("/Http/Error401");
break;
}
}
}
I'll be able to add more pages to the HttpContoller based on any additional HTTP error codes I need to support.
The HandleError attribute seems to only process exceptions thrown by the MVC infrastructure and not exceptions thrown by my own code.
That is just wrong. Indeed, HandleError will only "process" exceptions either thrown in your own code or in code called by your own code. In other words, only exceptions where your action is in the call stack.
The real explanation for the behavior you're seeing is the specific exception you're throwing. HandleError behaves differently with an HttpException. From the source code:
// If this is not an HTTP 500 (for example, if somebody throws an HTTP 404 from an action method),
// ignore it.
if (new HttpException(null, exception).GetHttpCode() != 500) {
return;
}
I don't think you will be able to show specific ErrorPage based upon the HttpCode with the HandleError Attribute and I would prefer to use an HttpModule for this purpose. Assuming that I have folder "ErrorPages" where different page exists for each specific error and the mapping is specifed in the web.config same as the regular web form application. And the following is the code which is used to show the error page:
public class ErrorHandler : BaseHttpModule{
public override void OnError(HttpContextBase context)
{
Exception e = context.Server.GetLastError().GetBaseException();
HttpException httpException = e as HttpException;
int statusCode = (int) HttpStatusCode.InternalServerError;
// Skip Page Not Found and Service not unavailable from logging
if (httpException != null)
{
statusCode = httpException.GetHttpCode();
if ((statusCode != (int) HttpStatusCode.NotFound) && (statusCode != (int) HttpStatusCode.ServiceUnavailable))
{
Log.Exception(e);
}
}
string redirectUrl = null;
if (context.IsCustomErrorEnabled)
{
CustomErrorsSection section = IoC.Resolve<IConfigurationManager>().GetSection<CustomErrorsSection>("system.web/customErrors");
if (section != null)
{
redirectUrl = section.DefaultRedirect;
if (httpException != null)
{
if (section.Errors.Count > 0)
{
CustomError item = section.Errors[statusCode.ToString(Constants.CurrentCulture)];
if (item != null)
{
redirectUrl = item.Redirect;
}
}
}
}
}
context.Response.Clear();
context.Response.StatusCode = statusCode;
context.Response.TrySkipIisCustomErrors = true;
context.ClearError();
if (!string.IsNullOrEmpty(redirectUrl))
{
context.Server.Transfer(redirectUrl);
}
}
}
One other possibility (not true in your case) that others reading this may be experiencing is that your error page is throwing an error itself, or is not implementing :
System.Web.Mvc.ViewPage<System.Web.Mvc.HandleErrorInfo>
If this is the case then you will get the default error page (otherwise you'd get an infinite loop because it would keep trying to send itself to your custom error page). This wasn't immediately obvious to me.
This model is the model sent to the error page. If your error page uses the same master page as the rest of your site and requires any other model information then you will need to either create your own [HandleError] type of attribute or override OnException or something.
protected override void OnException (ExceptionContext filterContext )
{
if (filterContext != null && filterContext.Exception != null)
{
filterContext.ExceptionHandled = true;
this.View("Error").ViewData["Exception"] = filterContext.Exception.Message;
this.View("Error").ExecuteResult(this.ControllerContext);
}
}
I chose the Controller.OnException() approach, which to me is the logical choice - since I've chosen ASP.NET MVC, I prefer to stay at the framework-level, and avoid messing with the underlying mechanics, if possible.
I ran into the following problem:
If the exception occurs within the view, the partial output from that view will appear on screen, together with the error-message.
I fixed this by clearing the response, before setting filterContext.Result - like this:
filterContext.HttpContext.Response.Clear(); // gets rid of any garbage
filterContext.Result = View("ErrorPage", data);
Jeff Atwood's User Friendly Exception Handling module works great for MVC. You can configure it entirely in your web.config, with no MVC project source code changes at all. However, it needs a small modification to return the original HTTP status rather than a 200 status. See this related forum post.
Basically, in Handler.vb, you can add something like:
' In the header...
Private _exHttpEx As HttpException = Nothing
' At the top of Public Sub HandleException(ByVal ex As Exception)...
HttpContext.Current.Response.StatusCode = 500
If TypeOf ex Is HttpException Then
_exHttpEx = CType(ex, HttpException)
HttpContext.Current.Response.StatusCode = _exHttpEx.GetHttpCode()
End If

Resources