WebApi RaygunWebApiActionFilter usage
drdimwit
Posted on
Apr 15 2015
In my API controller I want to be able to return a HttpResponseMessage with a HttpStatusCode of BadRequest. Along with this bad request I would also like to add set of custom data to the exception created explaining the bad request. This seems reasonable but as soon as I return the HttpResponseMessage the action filter picks up the OnActionExecuted and because there is not a exception on the HttpActionExecutedContext there a new RaygunWebApiHttpException created to which I have no chance to add the custom data to it.
It seems that if the HttpActionExecutedContext had an exception on it then the OnActionExecuted would just ignore the error and throw it up to the ExceptionLogger.
I am confused on how to proceed can someone provide an example please.
Thanks DD
Jason Fauchelle
Raygun
Posted on
Apr 15 2015
Hello,
For this scenario, we recommend that you listen to the SendingMessage event of the RaygunWebApiClient to attach custom data. To do this, you'll first need to provide a RaygunWebApiClient instance for the built-in filters and handlers to use. This is done using an overload of the Attach method (you should be calling this in the WebApiConfig Register method).
RaygunWebApiClient.Attach(config, () =>
{
var raygunClient = new RaygunWebApiClient();
raygunClient.SendingMessage += RaygunClient_SendingMessage;
return raygunClient;
});
The event handler:
private static void RaygunClient_SendingMessage(object sender, Mindscape.Raygun4Net.RaygunSendingMessageEventArgs e)
{
}
The function you pass to the Attach method will be called every time Raygun sends an exception report, so if you like, you may want to set up a single RaygunWebApiClient instance outside the function and then return that within the function.
In the event handler, e.Message will give you the RaygunMessage object that is about to be serialized and sent to Raygun. Here you can make any modifications you want to the RaygunMessage such as attaching custom data to e.Message.Details.UserCustomData. You may only want to do this for the BadRequest scenario, so you can check e.Message.Details.Error.ClassName to see if it is a RaygunWebApiHttpException.
Hope that helps, let us know if you have further questions about this.
-Jason Fauchelle
drdimwit
Posted on
Apr 15 2015
Hi Jason
Thanks for the reply. Here is the issue that I have with that solution.
When I create the HttpresponseMessage or the HttpResponseException in the controller action I am attaching the data that I would like to add as a typed exception and setting the status code to BadRequest.
The issue that arises when the action filter kicks in and a new RaygunWebApiHttpException is thrown. This new error has now lost the custom data that was added to the response. Then when the sending
message event handler fires the message that is being passed is the RaygunWebApiHttpException which does not have the data and thus I do not have access to the original data to add to the raygun message.
Is there someway that I can get that original data through the action filter so that it is not scrubbed?
Thanks DD
Jason Fauchelle
Raygun
Posted on
Apr 15 2015
Hello,
My previous suggestion will work if the custom data you want to send is accessible in your SendingMessage handler. i.e. rather than setting the data in your controller action, acquire it and attach it in the SendingMessage event handler. This can work for statically available custom data for example.
If the custom data you want to send is only available when you raise the exception, then there is another thing you can try. Since WebApi treats HttpResponseException specially and swallows them before Raygun4Net can see them, we recommend throwing a different type of exception in your controller action. You could either use one of the standard .NET exceptions, or implement your own exception, or even use the RaygunWebApiHttpException. Then attach your data to that. As long as it isn't an HttpResponseException, Raygun will be able to send it.
If you still need an HttpResponseException to be raised, then you could do this by implementing an ExceptionFilterAttribute. In the OnException method you can see if the exception is your chosen HttpResponseException replacement, and if so, raise a new HttpResponseException. Make sure this filter is set up to be called after the Raygun filters.
Data set on the Exception instance will be displayed in the Raw tab of your Raygun dashboard. For better visibility, you could set the custom data on the UserCustomData property I mentioned previously, which could be done in a SendingMessage event handler that transfers the data from the e.Message.Details.Error.Data property, to the e.Message.Details.UserCustomData property. This causes custom data to be displayed in it's own tab, or on the Summary tab if you like.
An alternative to all the above which should only be needed if the above does not work for you, would be to use a RaygunWebApiClient instance directly within your controller actions and call one of the Send or SendInBackground methods to send your exception. These methods have overloads for taking a dictionary of custom data.
Please let me know if you have further questions about any of this.
-Jason Fauchelle
Alec
Posted on
Aug 26 2015
I have some context information in the ASP.Net Http Session that I add to customdata for the regular MVC/WebForms Raygun error sending. I'm trying to do the same for WebApi but I hit a wall.
I was able to use the RaygunWebApiClient.Attach to set the User context for the error and I have access to HttpContext.Current there. However, I cannot set "Custom Data" there. Alternatively if I hook up the SendingMessage event I can set "Custom Data" there but for WebApi HttpContext.Current is null there so I cannot get at my session data!
Any ideas?
Jamie Penney
Posted on
Aug 26 2015
Hi Alec,
If you add an event handler to the SendingMessage
event on RaygunWebApiClient, you can set e.Message.Details.UserCustomData
to the custom data you want to send. While you're there you could also set the Tags
as well if you want.
Hope that helps.
Cheers, Jamie
Alec
Posted on
Aug 27 2015
Hi Jaime. Yes, I mentioned that SendingMessage event but I don't have access to HttpContext.Current there so can't get at the session data I want to add to custom data.
Jamie Penney
Posted on
Aug 27 2015
Sorry, you did mention that! Will teach me for not re-reading the question after mucking around in the code.
One thing you could do is use a lambda expression for the SendingMessage event handler
RaygunWebApiClient.Attach(config, requestMessage => {
var client = new RaygunWebApiClient();
client.SendingMessage += (sender, args) =>
{
// do your magic here, requestMessage is available.
};
return client;
}
That way the SendingMessage handler forms a closure over the requestMessage variable so you'll have access to it.
Alec
Posted on
Aug 27 2015
Hmm. Closer but not quite though I wonder if I'm running into a .NET limitation now...
For reference I have this in Global.asax
protected void Application_PostAuthorizeRequest()
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.ReadOnly);
}
The session is null when inside of the lamba. I did a POC showing how I can pull data from it outside the lambda and just tossed the SessionID in the version for reference but it goes to null inside.
RaygunWebApiClient.Attach(configuration, requestMessage => { var client = new RaygunWebApiClient();
// just an example to show session info is available here
client.ApplicationVersion = HttpContext.Current.Session.SessionID;
client.SendingMessage += (sender, args) =>
{
// HttpContet.Current is null here. Let's try the one stored in the requestMessage.
var httpContext = requestMessage.Properties["MS_HttpContext"] as HttpContextBase;
args.Message.Details.UserCustomData = new Dictionary<string, string>();
if (httpContext == null)
args.Message.Details.UserCustomData.Add("HttpContext", "NOT Available");
else if (httpContext.Session == null)
args.Message.Details.UserCustomData.Add("HttpContext", "NO Session"); // This is what I get here
else
args.Message.Details.UserCustomData.Add("HttpContext", httpContext.Session.SessionID);
};
return client;
});
Jamie Penney
Posted on
Aug 27 2015
That will be because we send exceptions on a background thread - https://github.com/MindscapeHQ/raygun4net/blob/master/Mindscape.Raygun4Net.WebApi/RaygunWebApiClient.cs#L395 That is why the HttpContext is null in your original message (I'd forgotten about this aspect).
I think you might need to extract the necessary information from the session outside the SendingMessage handler.
Alec
Posted on
Aug 27 2015
Can you give me an example of passing a simple string variable for example through the closure? I can't seem to get it going without a syntax error.
Jamie Penney
Posted on
Aug 27 2015
RaygunWebApiClient.Attach(config, requestMessage => {
var client = new RaygunWebApiClient();
string sessionID = HttpContext.Current.Session.SessionID;
client.SendingMessage += (sender, args) =>
{
args.Message.Details.UserCustomData.Add("HttpContext", sessionID);
};
return client;
});
You can use any variables from the outer scope as you normally would.
Alec
Posted on
Aug 27 2015
Ah. It works. I had tried that with a different lamba and I think I had just setup the previous closure incorrectly and couldn't access variables outside. This works great.
Here's the kicker. When reading a variable saved in the outer area from the session, the session stored in the message properties is now no longer null either so
args.Message.Details.UserCustomData.Add("HttpContext", httpContext.Session.SessionID);
works too! I tried it twice switching the code back and forth to make sure I wasn't seeing things. I wonder if the runtime is playing some tricks to also marshal the session data over or somehow not using a separate thread and running on the same one. In any case it works.
Thanks for your help!
Jamie Penney
Posted on
Aug 27 2015
Using the HttpContext inside the lambda is a bit dodgy, as the HttpContext gets disposed when the request thread cleans up. This may or may not happen before the background send thread is finished (yay multithreading issues). That is why I suggested grabbing the data you need in the setup code(which runs on the request thread), and just using it in the event handler (which runs on the background thread).
Stefan Kip
Posted on
Dec 02 2015
Thanks Jamie for the example, really useful! :-)