I have a hybrid (use both MVC and classic ASP pages) ASP (C#) Net Application
and need to implement common error handling for both MVC and legacy codes;
Namely, I have to detect invalid URLs and re-route the invalid request to
either home page or login page (depending whether the user is logged in or not).
I have added the error handling code inside the 'Application_Error' (See code below).
The issue is the following: loggedin user id is kept in Session object
and for some invalid URLs session object becomes null with: "session state is not available in this context"
For example:
for the following URLs, the Session object is present:
1. http://myserver:49589/test/home/index
2. http://myserver:49589/test/home/in
3. http://myserver:49589/test/ho
But for the following URL, the session object is null:
4. http://myserver:49589/te
So, the question is why session object becomes null when I misspell the folder name in the Request, and how I can solve this issue.
Routing Map is the following:
context.MapRoute(
"default",
"test/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional }
);
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
if (httpException != null) // Http Exception
{
switch (httpException.GetHttpCode())
{
case 400: // Bad Request
case 404: // Page Not Found
case 500: // Internal Server Error
{
// Clear the error on server.
Server.ClearError();
ServerConfiguration scfg = ServerConfiguration.Instance;
if (ConnxtGen.App.AppUtility.GetCurrentUserID() != -1)
{
Response.RedirectToRoute("Unity_default", new { controller = "Home", action = "Index" });
}
else
{
Response.Redirect(scfg.PagePath + "/login/login.aspx", false);
}
break;
}
default:
{
break;
}
}
}
// Avoid IIS7 getting in the middle
Response.TrySkipIisCustomErrors = true;
}
One thing you should understand is that Session variables are only available after the HttpApplication.AcquireRequestState event has happened.
In your question, the moment where you want to get some information in the session is too early in the process of the Asp.Net page lifecycle.
I found this post which will surely better explain when the session object will be available:
Asp.net What to do if current session is null?
And here is a great article that explain in deeper details the entire internal process of Asp.Net page lifecycle:
ASP.NET Application and Page Life Cycle
Related
I am catching unhandled errors using the Application_Error method within my Global.asax file, this is what I have so far:
protected void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
Response.Clear();
HttpException httpException = exception as HttpException;
if(httpException != null)
{
string action;
switch(httpException.GetHttpCode())
{
case 404:
action = "404";
break;
case 500:
action = "500";
break;
default:
action = "Other";
break;
}
Server.ClearError();
Response.Redirect(String.Format("~/Error/?error=" + action + "&message=" + exception.Message));
}
}
However I really don't like the idea of redirecting the user to error page, infact I would like the URL to remain the same.
For example when a page doesn't exist it shouldn't redirect to the path in the URL, instead it should remain on the same page but still display an error.
Does anyone know how to do this?
My solution redirects much like yours, but depending on how you deploy I would do this by customising the error pages themselves in IIS.
Check Out this answered question
I try to handle 404 and 500 error in ASP.NET MVC 6.
I have Action in controller which render page with error status and description:
[HttpGet]
[Route("/Errors/{status}")]
public IActionResult Errors(int status)
{
var model = Mapper.Map<ErrorViewModel>(BaseData);
model.ErrorCode = status;
if (error_descriptions.ContainsKey(status))
model.Description = error_descriptions[status];
else
model.Description = "Неизвестная ошибка";
return View(model);
}
I enable status code pages and exception handler:
app.UseExceptionHandler("/Errors/500");
app.UseStatusCodePagesWithReExecute("/Errors/{0}");
I want to ger 404 error when url doesn't match any route and when queryes to Database return empty sequencies (access to nonexists elements).
I've got InvalidOperationException when LINQ return empty sequence (the sequence contains no elements). I want to handle global exception, show 404 in this case and 500 in other.
I make a simple filter and check Exception in OnException:
public void OnException(ExceptionContext context)
{
logger.LogError(context.Exception.Message);
if (context.Exception is System.InvalidOperationException) ;
//NEED TO CALL HomeController.Error(404)
}
It works, but I don't know how to call my action and show error page
You can create a model in the OnException method like
public void OnException(ExceptionContext context)
{
var model = new HandleErrorInfo(filterContext.Exception, "YourError Controller","Errors");
}
I'm new to c# and asp mvc and I can't find a way to handle the exceptions globally, but returning to the same page from were they were generated. Right now in my controllers I have something like this in all the post methods:
try
{
service.Action(object);
return RedirectToAction("Index");
}
catch (MyBusinessException e)
{
ViewData["error"] = (e.InnerException.Message ?? "");
return View(object);
}
The message of the inner exception is a friendly message, like "You cannot create two articles with the same Code" that I want to show to the user in the form of create article rather than send him to an error page
I was trying to reproduce this behavior by creating a base controller that overrides the OnException method, but I don't know how to return to the same page from where I came and show the error message.
Edit based on response from #JotaBe
I've created a global OnException filter and registered in the FilterConfig.cs, now I'm able to return to the page from were I came, but the model that I get from the ExceptionContext it's null, therefore I get a NullPointerException when the view it's loaded.
This is my current method:
public override void OnException(System.Web.Mvc.ExceptionContext filterContext)
{
var currentController = (string)filterContext.RouteData.Values["controller"];
var currentActionName = (string)filterContext.RouteData.Values["action"];
LOGGER.ErrorFormat("Action: {0} - Controller: {1} - ExceptionMessage: {2} - InnerException: {3}",
currentActionName,
currentController,
filterContext.Exception.Message,
filterContext.Exception.InnerException.Message);
Exception e = filterContext.Exception;
filterContext.ExceptionHandled = true;
filterContext.Result = new ViewResult()
{
ViewName = currentActionName,
ViewData = filterContext.Controller.ViewData,
MasterName = Master,
TempData = filterContext.Controller.TempData
};
}
How can I obtain the model that was posted?
What you need to do is to use filters. A filter is an attribute that can be applied to controllers to modify their behavior. Here you have the official MSDN docs for MVC filters.
In the previous doc you can see that there is a filter named HandleErrorAttribute, which is the one that you need for your particular case.
A filter can be applied globally (registering it on application start) or decorating each individual controller. For a good explanation and sample, you can read this blog entry.
Remember that there can be execptions different to those thrown by you. So, you should take into account that possibility to show different information depending on the type of the exception. This filter allows to show different views depending on the exception type.
In my C# .NET 4 MVC 3 application I have a delete controller for a set of CRUD pages which uses the Post Redirect Get pattern to redirect to an Index controller after a successful delete. I would like to render a button on the Index page only if this page was NOT redirected to by such an action. Is there a simple way to detect if the current page was redirected to (i.e. was reached as the result of a PRG redirect)?
After reading http://blog.simonlovely.com/archive/2008/11/26/post-redirect-get-pattern-in-mvc.aspx my current approach is to set this in my delete controller with TempData after the DeleteMyEntity method has succeeded:
try {
MyService.DeleteMyEntity(MyViewModel.MyEntity);
TempData["Redirected"] = true;
args = new RouteValueDictionary(new { Foo = 1, Baa = 2 });
return RedirectToAction("Index", args);
} catch (Exception e)
{
//Logging etc. - redirect should never be reached on exception (and TempData item not set)
throw(e);
}
then in my Index controller I check to see if this value exists and is true:
if (TempData["Redirected"] != null)
{
//we can then do something useful with this
}
Another opportunity I see would be to add another item to args and check for this in the controller, but in this case I may as well just use TempData. Is there a way to do this using a HTTP Response code on the request without needing to pass this data through with TempData or a similar mechanism?
another route would be to set up a global actionfilter that "injects" that flag for you...
public class RedirectDetect: ActionFilterAttribute{
public override void OnActionExecuted(ActionExecutedContext filterContext){
if (filterContext.Result is RedirectToRouteResult ||
filterContext.Result is RedirectResult)
{
TempData["Redirected"] = true;
//or what ever other indicator you want to set
}
}
}
And then you can just call redirectToAction("Index") and then check in your receiving handler
sidenote: I challenge you to say RedirectDetect aloud and not smirk.
I use TempData in a similar fashion - for instance, to show a status message (after redirecting to) my view when a record has been added / updated / deleted. This is the kind of simple, throw-away stuff that TempData is used for, so I say what you have is appropriate.
Personally I wouldn't mess with HTTP status codes unless I had an absolute need for it. And you could probably do something with the referrer http header, but again, that would be much messier and more complicated than just using TempData. You have a clean, simple solution that works, I say go with what you have.
I am not aware of any simpler mechanism and have been using TempData for quite some time to implement Post-Redirect-Get features. As far as I know, this is specifically one of the reasons for TempData's existence. I would continue using it.
There is no way to tell the difference between requests as a result of a 3xx redirection or a straightforward user-initiated GET. The best way is to provide a querystring argument that is only appended by the redirection for the initial POST request, but there is nothing stopping the user from reloading the page with the same querystring.
Hmmm, or you could send a cookie with the redirection from the POST, then remove the cookie in the response of the subsequent GET, like so:
public ActionResult PostHandler(ViewModel model) {
SetCookie("redirected", "true"); // psuedocode
return Redirect("GetHandler2");
}
public ActionResult GetHandler2() {
if( GetCookie("redirected") == "true" ) {
// do something
}
DeleteCookie("redirected");
}
Building off of George's answer:
public class MarkRedirects : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is RedirectToActionResult ||
context.Result is RedirectResult)
{
// Obtain and verify the underlying IController
var controller = context.Controller as Controller;
if (controller != null)
controller.TempData["Redirected"] = true; // or set other dictionary data here
}
}
}
The conditional checks of context.Result can vary based on what method you used to redirect, for instance if you redirected the user via the RedirectToAction() method, context.Result is RedirectToActionResult will return true but context.Result is RedirectToRouteResult will not.
Because of this, you will want to change up that conditional based on your personal taste of how you redirect users. The current code would work for OP's situation.
If you're going to be using this everywhere, it may be wise to modify a base controller.
I'm currently working on a Session expired piece of logic for my ASP.NET 3.5 MVC 2 project to log out a user and redirect them to the AccountController LogOn action.
I have the following attribute on all my actions that care about session state, and this piece of code works in IE 8, but not Firefox 4 or Google Chrome 10. The symptom is when I attempt to navigate to a view represented by an action with my [SessionExpireFilter] attribute, the ctx.Session.IsNewSession property in the below code is evaluating as "true" every time, even if I'm only seconds into my 30-minute session.
public class SessionExpireFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check if session is supported
if (ctx.Session != null && ctx.Session.IsNewSession)
{
// If it says it is a new session, but an existing cookie exists, then it must
// have timed out
string sessionCookie = ctx.Request.Headers["Cookie"];
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0))
{
FormsAuthentication.SignOut();
ctx.Response.Redirect("~/Account/LogOn");
}
}
base.OnActionExecuting(filterContext);
}
}
Is there any way to figure out why Chrome and Firefox are behaving this way, but IE is not? Thanks in advance.
EDIT: This is not working in FF as I originally believed. I am being directed to my LogOn action immediately after logging in and attempting to access an action with my SessionExpireFilter attribute.
ASP.NET will create a new session for every request unless you store something in it.
Try adding the code below to your Global.asax. It works in my MVC2 and MVC3 apps with the same SessionExpireFilterAttribute.
protected void Session_Start()
{
Session["Dummy"] = 1;
}
We can add session_start method in Golbal.asax file in MVC appliaction.
protected void Session_Start(object sender, EventArgs e)
{
HttpContext.Current.Session.Add("UserId" , null);
}
Then when application starting your session will be created. and then session will be not isNewSession 'True', otherwise it will be always 'True'