Unable to Set Cookie in ASP.NET MVC IExceptionFilter - asp.net

I've implemented a custom IExceptionFilter to handle an exception some users are experiencing with a third party library our application is consuming. When this particular error state occurs, I need to modify the user's cookies (to clear their session state), but what I am finding is that no cookies seem to make it out of the filter. Not sure what's wrong or even how to debug it.
I've modified the functional bits filter to simplify the intent, but here's the gist of the filter. I've ensured that it is the first filter to run on the controller and tested removing the HandleErrorAttribute filter as well to no avail. After the below code runs, "somecookie" is never set on the client.
public class HandleSessionErrorAttribute : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (filterContext == null) throw new ArgumentNullException("filterContext");
var exception = filterContext.Exception as HttpException;
if (exception != null && exception.Message.Equals("The session state information is invalid and might be corrupted."))
{
filterContext.HttpContext.Response.Cookies.Add(new HttpCookie("somecookie")
{
Value = DateTime.Now.ToString(),
Expires = DateTime.Now.AddMinutes(5),
});
}
}
}

Okay, figured it out. Two issues were preventing the logic from succeeding:
The HandleErrorAttribute must run before any attribute which modifies the response. Part of the implementation of HandleErrorAttribute is to Clear() the response.
CustomErrors must be On for this to work
The initialization code which worked:
GlobalFilters.Filters.Add(new HandleErrorAttribute() { Order = 0 });
GlobalFilters.Filters.Add(new HandleSessionErrorAttribute() { Order = 1 });

Related

AiHandleErrorAttribute Vs. Built-In auto added Action Filter provided by Application Insights

I just installed Application Insights into my ASP.NET MVC application. It's actually an Umbraco website, and the registration is slightly different but the result should be the same.
When installing the package, it added some code for me to register a new Exception Action Filter globally called 'AiHandleErrorAttribute'.
I'm registering it the Umbraco way using an event handler:
public class RegisterAIEventHandler : ApplicationEventHandler
{
protected override void ApplicationInitialized(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
base.ApplicationInitialized(umbracoApplication, applicationContext);
GlobalFilters.Filters.Add(new ErrorHandler.AiHandleErrorAttribute());
}
}
And this is the Action Filter code:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class AiHandleErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext != null && filterContext.HttpContext != null && filterContext.Exception != null)
{
//If customError is Off, then AI HTTPModule will report the exception
if (filterContext.HttpContext.IsCustomErrorEnabled)
{
var ai = new TelemetryClient();
ai.TrackException(filterContext.Exception);
}
}
base.OnException(filterContext);
}
}
Whenever an exception is thrown, the Action Filter isn't triggered but the Exception is still recorded correctly in Application Insights.
When I inspect all Global Action Filters, I noticed there's another Action Filter registered by Application Insights automatically.
So I have few questions:
Is the AppInsights Action Filter that automatically got registered preventing my filter from being run?
Do I even need the custom Action Filter that AppInsights generated for me, since the exceptions seem to be captured by the other Action Filter added by AppInsights?
Should I remove the Action Filter added by AppInsights or the custom action filter?
Edit: The reason the custom Action Filter isn't triggered is because the exception I was purposely setting off was thrown before I got into the Controller Pipeline. Now that I'm triggering an exception inside of the controller, it actually gets called.
Though, I still question why there's an Action filter added automatically, and if I should add the custom AiHandleErrorAttribute as well.
I also just ran into this. My exceptions were being logged twice in AI.
As it turns out, starting with version 2.6 (April 2018) a global filter is automatically added. If you had previously followed the documentation and set it up yourself, everything would now be logged twice.
The global filter that is added looks like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class MvcExceptionFilter : HandleErrorAttribute
{
public const bool IsAutoInjected = true;
private readonly TelemetryClient telemetryClient = new TelemetryClient();
public MvcExceptionFilter(TelemetryClient tc) : base()
{
telemetryClient = tc;
}
public override void OnException(ExceptionContext filterContext)
{
if (filterContext != null && filterContext.HttpContext != null && filterContext.Exception != null && filterContext.HttpContext.IsCustomErrorEnabled)
telemetryClient.TrackException(new ExceptionTelemetry(filterContext.Exception));
}
}
}
If you haven't added anything to the AiHandleErrorAttribute that was given in the documentation previously, you can remove it and let the automatically generated one handle everything.
If you do want to use your own version, to have more control over it (for example ignoring certain exceptions), you can disable the automatic exception tracking.
Add this to your ApplicationInsights.config:
<Add Type="Microsoft.ApplicationInsights.Web.ExceptionTrackingTelemetryModule, Microsoft.AI.Web">
<EnableMvcAndWebApiExceptionAutoTracking>false</EnableMvcAndWebApiExceptionAutoTracking>
</Add>
Note that the ExceptionTrackingTelemetryModule element will already exist, you just have to add the setting to it.

Override Elmah logged error message

Is there any way of overriding the error message logged by Elmah without duplicating it?
I have a custom exception class:
public class BusinessException : Exception
{
// detailed error message used for logging / debugging
public string InternalErrorMessage { get; set; }
public BusinessException(string message, string internalMessage)
:base(message)
{
InternalErrorMessage = internalMessage;
}
}
From the code, i throw an exception like this:
string detailedErrorMessage = string.Format("User {0} does not have permissions to access CreateProduct resource", User.Identity.Name);
throw new BusinessException("Permission denied", detailedErrorMessage);
When Elmah logs the error, it only logs Permission denied message. However, i need to log the InternalErrorMessage property of the exception instead.
I've tried to create a custom HandleErrorAttribute to do this, but this duplicates the exceptions logged:
public class ErrorHandleAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
if (filterContext.ExceptionHandled == true)
return;
Exception exception = filterContext.Exception;
BusinessException businessException = exception as BusinessException;
if (businessException != null)
{
ErrorSignal.FromCurrentContext().Raise(new Exception(businessException.InternalErrorMessage, exception));
}
}
}
I think your issue might be here:
if (businessException != null) {
ErrorSignal.FromCurrentContext().Raise(
new Exception(businessException.InternalErrorMessage, exception));
}
When you create a new exception rather than something like this:
if (businessException != null) {
ErrorSignal.FromCurrentContext().Raise(businessException));
}
I have this done the code for one of my sites and can see if I can recreate your issue later today (assuming this does not work). I think this is the SO post that helped me implement: How to get ELMAH to work with ASP.NET MVC [HandleError] attribute?
Edit:
Re-reading your question and the other answer I realize I was trying to solve your attempted correction and not the actual problem. Have you tried something like this solution which is only a slight deviation from your current attempt:
public override void OnException(ExceptionContext filterContext) {
if (filterContext.ExceptionHandled == true) {
return;
}
Exception exception = filterContext.Exception;
BusinessException businessException = exception as BusinessException;
if (businessException != null) {
var customEx = new Exception(
businessException.InternalErrorMessage, new BusinessException());
ErrorSignal.FromCurrentContext().Raise(customEx);
return;
}
}
Check that the InternalErrormessage is returning what you expect, and I expect that forcing a return here will prevent the exception from being logged twice. Otherwise it is essentially what you had done.
I suspect you'll need to create your own errorlog implementation to log anything other than the standard properties. This shouldn't be too difficult.
Using the SqlErrorLog as an example, you only need to override the log method & put in your own logic to modify what the Error class contains before calling the base implementation.
Using what #Matthew has also said will stop you logging the exception twice.
All the source code is here

How to prevent Elmah logging errors handled in a Error Attribute

I am using Elmah and Elmah.mvc packages in a asp.net, mvc4 web app. I have a specific controller action where I want to handle HttpAntiForgeryExceptions in a specific manner. I have created a custom HandleErrorAttribute that inherits from HandleErrorAttribute and implements IExceptionFilter.
Under the specific circumstances I want to handle, I set the ExceptionContext.ExceptionHandled to true. The behaviour that the user sees is correct and the error is handled as I want it to be. However, it also logs an error to Elmah, which I don't want it to do, as I would like to keep the Elmah log for true errors.
The controller annotation looks like:
[ValidateAntiForgeryToken]
[CustomHandleAntiforgeryError]
public ActionResult ControllerMethod(Model model)
{
...
}
The CustomHandleAntiforgeryError looks like:
public class CustomHandleAntiforgeryErrorAttribute:
HandleErrorAttribute, IExceptionFilter
{
public override void OnException(ExceptionContext filterContext)
{
if (circumstancesAreOk)
{
filterContext.ExceptionHandled = true;
return;
}
}
}
Is there anything else I need to do to prevent this error being logged with Elmah?
--- EDIT ---
Looking at the Elmah.MVC source the HandleErrorAttribute logs both handled and unhandled errors
public override void OnException(ExceptionContext context)
{
base.OnException(context);
if (!context.ExceptionHandled) // if unhandled, will be logged anyhow
return;
var e = context.Exception;
var httpContext = context.HttpContext.ApplicationInstance.Context;
if (httpContext != null &&
(RaiseErrorSignal(e, httpContext) // prefer signaling, if possible
|| IsFiltered(e, httpContext))) // filtered?
return;
LogException(e, httpContext);
}
I would like a way within my custom attribute to signal to Elmah not to log this error and would appreciate any ideas.
See ErrorFiltering from Elmah's documentation. Here's the introduction:
When an unhandled exception is reported to ELMAH by ASP.NET, an
application can decide whether to dismiss the exception or not. There
are two ways for an application to do this, either programmatically or
declaratively via the configuration file. The simpler of the two is
programmatically because you do not need to learn anything new except
write an event handler in your favorite language. The downside of the
programmatic approach is that you need to write code and modify your
web application (requiring possibly a static re-compile). With the
configuration-based approach, you can simply apply filtering of
exceptions to a running application.
What I did to solve this I think is ugly, but worked for the weird error filter corner case that I was experiencing. I added a custom HandleErrorAttribute, copied from the Elmah HandleErrorAttribute and included a null check in the OnException method.
public override void OnException(ExceptionContext context)
{
base.OnException(context);
if (!context.ExceptionHandled) // if unhandled, will be logged anyhow
return;
string[] formKeys = context.HttpContext.Request.Form.AllKeys;
var e = context.Exception;
// linked to CustomErrorAttribute
if (e == null)
{
return;
}
bool test = HostingEnvironment.IsHosted;
var httpContext = context.HttpContext.ApplicationInstance.Context;
if (httpContext != null &&
(RaiseErrorSignal(e, httpContext) // prefer signaling, if possible
|| IsFiltered(e, httpContext))) // filtered?
return;
LogException(e, httpContext);
}
Then, in my error filter, which I did not want to trigger messaging to Elmah, as well as setting the ExceptionContext ExceptionHandled to true I set the Exception to null
public class MyCustomErrorFilter : IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
if (blah, blah, blah, weird things but not something terrible)
{
filterContext.Exception = null;
filterContext.ExceptionHandled = true;
return;
}
}
}
If it was something more involved and a regular occurrence I would probably fork Elmah and look at creating a custom Elmah build as this feels a little bit hacky to rely on in multiple situations.

Setting exception handled in Web API ExceptionFilterAttribute

Is there any way in ASP.NET Web API to mark an exception as handled in an ExceptionFilterAttribute?
I want to handle the exception at the method level with an exception filter and stop the propagation to a globally registered exception filter.
Filter used on a controller action:
public class MethodExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is NotImplementedException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.InternalServerError)
{
Content = new StringContent(context.Exception.Message)
};
// here in MVC you could set context.ExceptionHandled = true;
}
}
}
The globally registered filter:
public class GlobalExceptionFilterAttribute : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
if (context.Exception is SomeOtherException)
{
context.Response = new HttpResponseMessage(HttpStatusCode.SomethingElse)
{
Content = new StringContent(context.Exception.Message)
};
}
}
}
Try throwing an HttpResponseException at the end of your local handling. By design, they are not caught by exception filters.
throw new HttpResponseException(context.Response);
Web API 2 is designed with inversion of control in mind. You consider the possibility for the exception to already be handled, rather than interrupting the filter execution after you handle it.
In this sense, attributes deriving from ExceptionFilterAttribute should check if the exception is already handled, which your code already does since is operator returns false for null values. In addition, after you handle the exception, you set context.Exception to null in order to avoid further handling.
To achieve this in your code, you need to replace your comment from MethodExceptionFilterAttribute with context.Exception = null to clear the exception.
It is important to note that it is not a good idea to register more than one global exception filter, due to ordering issues. For information about the execution order of attribute filters in Web API, see the following thread Order of execution with multiple filters in web api.

ASP.NET Cache and File Dependancies

I want a ASP.NET cache item to be recycled when a specific file is touched, but the following code is not working:
HttpContext.Current.Cache.Insert(
"Key",
SomeObject,
new CacheDependency(Server.MapPath("SomeFile.txt")),
DateTime.MaxValue,
TimeSpan.Zero,
CacheItemPriority.High,
null);
"SomeFile.txt" does not seem to be checked when I'm hitting the cache, and modifying it does not cause this item to be invalidated.
What am I doing wrong?
Problem Solved:
This was a unique and interesting problem, so I'm going to document the cause and solution here as an Answer, for future searchers.
Something I left out in my question was that this cache insertion was happening in a service class implementing the singleton pattern.
In a nutshell:
public class Service
{
private static readonly Service _Instance = new Service();
static Service () { }
private Service () { }
public static Service Instance
{
get { return _Instance; }
}
// The expensive data that this service exposes
private someObject _data = null;
public someObject Data
{
get
{
if (_data == null)
loadData();
return _data;
}
}
private void loadData()
{
_data = GetFromCache();
if (_data == null)
{
// Get the data from our datasource
_data = ExpensiveDataSourceGet();
// Insert into Cache
HttpContext.Current.Cache.Insert(etc);
}
}
}
It may be obvious to some, but the culprit here is lazy loading within the singleton pattern. I was so caught up thinking that the cache wasn't being invalidated, that I forgot that the state of the singleton would be persisted for as long as the worker process was alive.
Cache.Insert has an overload that allows you to specify a event handler for when the cache item is removed, my first test was to create a dummy handler and set a breakpoint within it. Once I saw that the cache was being cleared, I realized that "_data" was not being reset to null, so the next request to the singleton loaded the lazy loaded value.
In a sense, I was double caching, though the singleton cache was very short lived, but long enough to be annoying.
The solution?
HttpContext.Current.Cache.Insert(
"Key",
SomeObject,
new CacheDependency(Server.MapPath("SomeFile.txt")),
DateTime.MaxValue,
TimeSpan.Zero,
CacheItemPriority.High,
delegate(string key, object value, CacheItemRemovedReason reason)
{
_data = null;
}
);
When the cache is cleared, the state within the singleton must also be cleared...problem solved.
Lesson learned here? Don't put state in a singleton.
Is ASP.NET running under an account with the proper permissions for the file specified in the CacheDependency? If not, then this might be one reason why the CacheDependency is not working properly.
I think you'll need to specify a path:
var d = new CacheDependency(Server.MapPath("SomeFile.txt"));
Prepend with ~\App_Data as needed.
Your code looks fine to me. However, beyond this snippet, anything could be going on.
Are you re-inserting on every postback by any chance?
Try making your cache dependency a class field, and checking it on every postback. Modify the file in between and see if it ever registers as "Changed". e.g.:
public partial class _Default : System.Web.UI.Page
{
CacheDependency dep;
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
dep = new CacheDependency(Server.MapPath("SomeFile.txt"));
HttpContext.Current.Cache.Insert(
"Key",
new Object(),
dep,
DateTime.MaxValue,
TimeSpan.Zero, CacheItemPriority.High, null);
}
if (dep.HasChanged)
Response.Write("changed!");
else
Response.Write("no change :("); }}
The only way I am able to reproduce this behavior is if the path provided to the constructor of CacheDependency does not exist. The CacheDependency will not throw an exception if the path doesn't exist, so it can be a little misleading.

Resources