Error handling and logging in ASP.NET - asp.net-mvc

I have the following requirements for a web app, that consist of MVC 5 pages and WebApi 2 services:
All errors (404, 500, "A potentially dangerous Request.Form value was detected from the client") should produce corresponding error pages in place (no browser redirect, keep the URL the user entered in the address bar).
All error pages should be rendered using dynamic MVC views and contain a unique ID, that the user can give to phone support.
All errors must be logged, the log entries should contain the Id given to the user.
Produce different error pages for different exceptions, e.g. /error/NoSignal for NoSignalException.
Is this doable?

I would override exception hangling method - in each controller or in base (depends on your decision).
protected override void OnException(ExceptionContext filterContext)
{
filterContext.Exception.ToString(); // - make any checks here
// If method is not implemented
if (filterContext.Exception.GetType() == typeof(NotImplementedException))
{
filterContext.Result = new ViewResult { ViewName = "NotImplemented" };
filterContext.ExceptionHandled = true;
}
}

Related

MVC3: Session_Start Fires twice when testing for Roles

I need to do some authentication for a web app with MVC3. The customer would like there to be a generic page to show if they do not have any of the role groups in windows AD that are allowed to use the app. I found a pretty simple way to do it, but just curious if it is a valid way or if there is something better out there.
Basically in the Session_Start in the global I am checking for User.IsInRole() and if that returns false then I do a Response.Redirect(). This question is: after it his the code in the IF statement and hits the Response.Redirect() code then it hits the session one more time before it goes to the AccessDenied page in the root of the app. Is this okay? Will it cause any issues If they are valid and does not enter the If to do the response.redirect?
//if (!User.IsInRole("test_user"))
//{
// Response.Redirect("~/AccessDenied.aspx", true);
//}
I would recommend you to write your Authorization filter for MVC3 and do this type of logic there:
public class RoleFilter: AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext filterContext)
{
if (!User.IsInRole("test_user"))
{
filterContext.HttpContext.Response.StatusCode = 302;
filterContext.Result = new RedirectResult("~/AcessDenied.aspx");
}
}
}
Also I wouldn't recommend you to use Response.Redirect because it aborts current thread.

MVC Custom error and exception handling - standard through out the project

I am new to ASP.NET MVC , Kendo UI (razor) , Jquery. I have an application that throws 3 kinds of errors
Unhanded exceptions (400, 403,500 503 etc) - throw generic exceptions
Expected exceptions/ errors (custom exceptions) - e.g. Trying to create a contact that already exist in the system - The system needs to throw "Contact_duplicated_exception" and show this to the user as "Contact was previously created. This action could not proceed".
Model state errors (UI). Errors that I add to modelstate to showup on the page using #Html.ValidationSummary(true)
What is the best standard way of handling the above throughout the application ?
I need to send these messages back to the user using Jquery Ajax [POST].
I have used the following concepts but I need to implement a standard way of dealing with the above
1. I have used ELMAH (for unhanded exceptions)
2. Application_Error(object sender, EventArgs e) in global.asax.cs
3. Custom HandleErrorArrtibute
public class HandleErrorWithAjaxFilter : HandleErrorAttribute, IExceptionFilter
Thanks!
For #1 and #3, you're looking good, IMHO. However, I think your weak-point is #2 and here is why:
If the exception is expected it should not be allowed to fall to Application_Error; because, well... it's expected, it's workflow, not an exception.
Therefore as a reaction to user input and part of workflow, it should be handled as part of #3.
So, in your shoes, I would go about in the specific instance of finding duplicates adding a validation attribute onto the potentially duplicate class like so:
[AttributeUsage(AttributeTargets.Class)]
public class UniqueContactAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
bool isValid = true;
Contact contact = value as Contact;
if(contact != null)
{
// check for your duplicate in the database and set isValid to false if you find one.
}
return isValid;
}
}
Usage for your metadata class:
[UniqueContact(ErrorMessage = "Contact was previously created. This action could not proceed.")]
public class Contact_Validation
{
}

ASP.NET MVC4 CustomErrors DefaultRedirect Ignored

I have an MVC 4 app, using a custom HandleErrorAttribute to handle only custom exceptions. I would like to intercept the default 404 and other non-500 error pages and replace them with something more attractive. To that end, I added the following to my Web.config:
<system.web>
<customErrors mode="On" defaultRedirect="~/Error/Index" />
...
</ system.web>
I have an Error controller with an Index method and corresponding view, but still I get the default 404 error page. I have also tried setting my defaultRedirect to a static html file to no avail. I have tried adding error handling specific to 404's inside <customErrors>, and I even tried modifying the routes programattically, all with no results. What am I missing? Why is ASP ignoring my default error handling?
Note: I noticed earlier that I cannot test my CustomHandleErrorAttribute locally, even with <customErrors mode="On". It does work when I hit it on my server from my dev box though... not sure if that is related. This guy had the same problem.
This should work :
1. Web.Config
<customErrors mode="On"
defaultRedirect="~/Views/Shared/Error.cshtml">
<error statusCode="403"
redirect="~/Views/Shared/UnauthorizedAccess.cshtml" />
<error statusCode="404"
redirect="~/Views/Shared/FileNotFound.cshtml" />
</customErrors>
2. Registered HandleErrorAttribute as a global action filter in the FilterConfig class as follows
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new CustomHandleErrorAttribute());
filters.Add(new AuthorizeAttribute());
}
If that dont work then, Try to make yourself transfer the response by checking status codes like the Following in the Global.asax: at least it must work.
void Application_EndRequest(object sender, EventArgs e)
{
if (Response.StatusCode == 401)
{
Response.ClearContent();
Server.Transfer("~/Views/Shared/UnauthorizedAccess.cshtml");
}
}
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.
Back to the original Query
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 404:
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:
Hope this should help you.
I want to share my knowledge after investigating this problem. Any comments that help improve my statements are welcomed.
In ASP.NET MVC, there are three layers that handle HTTP requests in the following order (response is transferred in reverse order):
IIS (HTTP Layer)
ASP.NET (Server Layer)
Controller (MVC Layer)
All of these layers have error handling, but each layer does it differently. I'll start with IIS.
IIS Layer
The simplest example of how IIS handles an error is to request a non existing .html file from your server, using the browser. The address should look like:
http://localhost:50123/this_does_not_exist.html
Notice the title of the browser tab, for example: IIS 10.0 Detailed Error - 404.0 - Not Found.
ASP.NET Layer
When IIS receives a HTTP request, if the URL ends with .aspx, it forwards it to ASP.NET since it is registered to handle this extension. The simplest example of how ASP.NET handles an error is to request a non existing .aspx file from your server, using the browser. The address should look like:
http://localhost:50123/this_does_not_exist.aspx
Notice the Version Information displayed at the bottom of the page, indicating the version of ASP.NET.
The customErrors tag was originally created for ASP.NET. It has effect only when the response is created by ASP.NET internal code. This means that it does not affect responses created from application code. In addition, if the response returned by ASP.NET has no content and has an error status code (4xx or 5xx), then IIS will replace the response according to the status code. I'll provide some examples.
If the Page_Load method contains Response.StatusCode = 404, then the content is displayed normally. If additional code Response.SuppressContent = true is added, then IIS intervenes and handles 404 error in the same way as when requesting "this_does_not_exist.html". An ASP.NET response with no content and status code 2xx is not affected.
When ASP.NET is unable to complete the request using application code, it will handle it using internal code. See the following examples.
If an URL cannot be resolved, ASP.NET generates a response by itself. By default, it creates a 404 response with HTML body containing details about the problem. The customErrors can be used to create a 302 (Redirect) response instead. However, accessing a valid URL that returns 404 response from application code does not trigger the redirect specified by customErrors.
The same happens when ASP.NET catches an exception from application code. By default, it creates a 500 response with HTML body containing details about the source code that caused the exception. Again, the customErrors can be used to generate a 302 (Redirect) response instead. However, creating a 500 response from application code does not trigger the redirect specified by customErrors.
The defaultRedirect and error tags are pretty straight-forth to understand considering what I just said. The error tag is used to specify a redirect for a specific status code. If there is no corresponding error tag, then the defaultRedirect will be used. The redirect URL can point to anything that the server can handle, including controller action.
MVC Layer
With ASP.NET MVC things get more complicated. Firstly, there may be two "Web.config" files, one in the root and one in the Views folder. I want to note that the default "Web.config" from Views does two things of interest to this thread:
It disables handling URLs to .cshtml files (webpages:Enabled set to false)
It prevents direct access to any content inside the Views folder (BlockViewHandler)
In the case of ASP.NET MVC, the HandleErrorAttribute may be added to GlobalFilters, which also takes into account the value of mode attribute of the customErrors tag from the root "Web.config". More specifically, when the setting is On, it enables error handling at MVC Layer for uncaught exceptions in controller/action code. Rather than forwarding them to ASP.NET, it renders Views/Shared/Error.cshtml by default. This can be changed by setting the View property of HandleErrorAttribute.
Error handling at MVC Layer starts after the controller/action is resolved, based on the Request URL. For example, a request that doesn't fulfill the action's parameters is handled at MVC Layer. However, if a POST request has no matching controller/action that can handle POST, then the error is handled at ASP.NET Layer.
I have used ASP.NET MVC 5 for testing. There seems to be no difference between IIS and IIS Express regarding error handling.
Answer
The only reason I could think of why customErrors is not considered is because they are created with HttpStatusCodeResponse from application code. In this case, the response is not altered by ASP.NET or IIS. At this point configuring an alternative page is pointless. Here is an example code that reproduces this behavior:
public ActionResult Unhandled404Error()
{
return new HttpStatusCodeResult(HttpStatusCode.NotFound);
}
In such scenario, I recommend implementing an ActionFilterAttribute that will override OnResultExecuted and do something like the following:
int statusCode = filterContext.HttpContext.Response.StatusCode;
if(statusCode >= 400)
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.Redirect("/Home/Index");
}
The implemented ActionFilterAttribute should be added to GlobalFilters.
Create a Controller ErrorController.
public class ErrorController : Controller
{
//
// GET: /Error/
public ActionResult Index()
{
return View();
}
}
Create the Index view for the action.
in Web.config
<customErrors mode="On">
<error statusCode="404" redirect="Error/Index"/>
</customErrors>
When you are handling errors in your code/logic
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start application.";
return View("Index2");
}
}
[HandleError] attribute - will redirected to the Error.cshtml page inside shared folder.
I am not sure this answer will help you but this a simple way... I placed error.html in / and turned mode to on for custom errors in web config and this works perfectly...
<system.web>
<customErrors defaultRedirect="~/Error.html" mode="On" />
</system.web>
this error.html is a basic html page with head and body..
To me, it works deleting the default Error.cshtml file, now it is taking the custom Error defaultRedirect page in Web.config.

How to validate a path in ASP.NET MVC 2?

I have a custom attribute that checks conditions and redirects the user to parts of the application as is necessary per business requirements. The code below is typical:
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// ...
if (condition)
{
RouteValueDictionary redirectTargetDictionary = new RouteValueDictionary();
redirectTargetDictionary.Add("action", "MyActionName");
redirectTargetDictionary.Add("controller", "MyControllerName");
filterContext.Result = new RedirectToRouteResult(redirectTargetDictionary);
}
// ...
base.OnActionExecuting(filterContext);
}
I was just asked to allow the user to choose a default page that they arrive at upon logging in. Upon adding this feature, I noticed that the user can get some unusual behavior if there is no action/controller corresponding to the user's default page (i.e. if the application were modified). I'm currently using something like the code below but I'm thinking about going to explicit actions/controllers.
else if (condition)
{
var path = "~/MyControllerName/MyActionName";
filterContext.Result = new RedirectResult(path);
}
How do I check the validity of the result before I assign it to filterContext.Result? I want to be sure it corresponds to a working part of my application before I redirect - otherwise I won't assign it to filterContext.Result.
I don't have a finished answer, but a start would be to go to the RouteTable, get the collection, call GetRouteData with a custom implementation of HttpContextBase to get the RouteData. When done, if not null, check if the Handler is an MvcRouteHandler.
When you've got so far, check out this answer :)

With ASP.NET membership, how can I show a 403?

By default, ASP.NET's membership provider redirects to a loginUrl when a user is not authorized to access a protected page.
Is there a way to display a custom 403 error page without redirecting the user?
I'd like to avoid sending users to the login page and having the ReturnUrl query string in the address bar.
I'm using MVC (and the Authorize attribute) if anyone has any MVC-specific advice.
Thanks!
I ended up just creating a custom Authorize class that returns my Forbidden view.
It works perfectly.
public class ForbiddenAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
if (AuthorizeCore(filterContext.HttpContext))
{
// ** IMPORTANT **
// Since we're performing authorization at the action level, the authorization code runs
// after the output caching module. In the worst case this could allow an authorized user
// to cause the page to be cached, then an unauthorized user would later be served the
// cached page. We work around this by telling proxies not to cache the sensitive page,
// then we hook our custom authorization code into the caching mechanism so that we have
// the final say on whether a page should be served from the cache.
HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null /* data */);
}
else
{
// auth failed, display 403 page
filterContext.HttpContext.Response.StatusCode = 403;
ViewResult forbiddenView = new ViewResult();
forbiddenView.ViewName = "Forbidden";
filterContext.Result = forbiddenView;
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Asp.net has had what I consider a bug in the formsauth handling of unauthenticated vs underauthenticated requests since 2.0.
After hacking around like everyone else for years I finally got fed up and fixed it. You may be able to use it out of the box but if not I am certain that with minor mods it will suit your needs.
be sure to report success or failure if you do decide to use it and I will update the article.
http://www.codeproject.com/Articles/39062/Salient-Web-Security-AccessControlModule.aspx

Resources