Error Handling
in Sitefinity
Introduction
Without any attention, errors in Sitefinity will throw the 'Yellow Screen of Death'. Helpful if your a developer wanting to know what is wrong with the page but terrible for visitors and business owners. In this post I will walk through some of the steps I put in place at the start of creating a Sitefinity site.
Update: 26 July 2014 - Added details about Elmah applicationName so JavaScript errors appear.
One of the first things we want to handle are HTTP status code errors. Primarily the 404, for un-found pages and 500, for page load crashes or server errors. The general solution when these status codes happen is to redirect the user to special page informing the user of the issue.
You have two choices.
Create a page in the CMS as normal. But you may be doomed if the error is in your master page.
Create static html pages that are totally self contained and can be served no matter what the state of the CMS. As long as the host is still serving files.
Me, I choose a page in the CMS using my already built theme. Its just easier. In regards to the 'what if scenarios' - I am happy to risk them in most cases. Doesn't mean I am right! So do decide that for yourself.
So after creating my 404 and 500 page. (That's what I name them.) Its time to wire them up. Open your web.config find the <system.webserver> and enter the redirects.
<system.webServer> <httpErrors errorMode="DetailedLocalOnly" existingResponse="Auto"> <remove statusCode="404" /> <remove statusCode="500" /> <error statusCode="404" responseMode="ExecuteURL" path="/404" /> <error statusCode="500" responseMode="ExecuteURL" path="/500" /> </httpErrors>
When running and testing on my PC or the host I want to see detailed error messages thus the DetailedLocalOnly value.
The response mode of ExecuteURL means that the path represents a relative path. Redirect means an absolute path.
Me, my first impression was to use Redirect and thought that a relative path would work. I was wrong! And now I have just saved you an hour of your life and several donations to the swear jar.
In the sample above I have added the 500 status code to show you but in practice I only add this in for production releases via the web transform file.
So great, that all works
But while 404's are much easier to sort out, a 500 error are a different story. We need the detail to figure out what caused the error. Just the fact that a client ended up on the page is not very helpful.
First we have to log the error. Sitefinity has its own logging but I highly encourage you to implement Elmah which is supported by Sitefinity.
At the time of this writing the Sitefinity documentation is missing a vital bit of info. You need to first install Elmah itself from Nuget and then Elmah on SQL Server. Hopefully they fix that info soon.
Follow their instructions and don't forget to turn Elmah logging on in the Sitefinity back-end. (Steps 5 and 6). I then also updated the web.config again.
<elmah> <security allowRemoteAccess="1" /> <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="elmah-sqlserver" applicationName="My App Name" />
</elmah>
Set the allowRemoteAccess to 1 so you don't have to be logged on locally to view the Elmah log.
Set the applicationName to your chosen name. If no name is specified then Elmah tries to look up the IIS name which resembles something like '/LM/W3SVC/41/ROOT'. Not wanting to ruin the extra surprise at the end so read on to see why you want to do this.
<location path="sitefinity/elmah.axd" inheritInChildApplications="false">
Then under the new location element I set the Elmah access page to be after Sitefinity.
You can also set up filtering through Elmah to ignore certain errors to avoid your log being filled up with stuff you don't care about. There are also email alerts. For myself, if there is an error in the log then it needs to be dealt with.
One case I had many ThreadAbortExceptions. These were being thrown by a Response.Redirect() call which I could not change. So I filtered these errors out of being recorded.
FYI
When you call Response.Redirect("~/myUrl"); This will throw a ThreadAbortException as processing after the method is stopped.
What you should call is
// Sets the page for redirect, but does not abort.
Response.Redirect("~/myUrl", false); // Causes ASP.NET to bypass all events and filtering in the HTTP pipeline
// chain of execution and directly execute the EndRequest event.
HttpContext.Current.ApplicationInstance.CompleteRequest(); // By setting this to false, we flag that a redirect is set,
// and to not render the page contents.
Page.Visible = false;
As I am writing this I found a good article that explains the reasoning and informed me of something I didn't know. The last line of setting the page visibility to false. Go check it out. Its a 2 minute read and very informative.
Back to our topic
Follow the documentation to test logging and you should see errors being logged. Turn off Detailed logging and have the 500 status codes enabled and you should see yourself redirected to the 500 page. Log into the back-end and check the Elmah page to see the recorded error. You should also test to ensure that you have to be logged in to see the Elmah error page. Cool. This what we want.
But...
Users still get just a 500 error page and we have to troll through logs trying to match up the related error. What we really want to do is give them a correlation code so we can find the exact error.
The Last Error Widget
So when an error happens its recorded in the database and the user is redirected to the 500 page. I created a simple widget for my 500 page to look up the last error in the database and return and display the id to the user so they can pass that to me and I can look up the error.
<div class="sfErrorIds"> <h4><asp:Literal ID="ErrorId" runat="server"/></h4> <h4><asp:Literal ID="SessionId" runat="server"/></h4> <h4><asp:Literal ID="DateUtc" runat="server"/></h4> </div>
protected override void InitializeControls(GenericContainer container) { String errorId = "No id recorded"; String sessionId = "No session recorded"; if(Page.Session != null) { sessionId = Page.Session.SessionID; System.Collections.IList errorList = new System.Collections.ArrayList(); Elmah.ErrorLog.GetDefault(this.Context).GetErrors(0, 1, errorList); if (errorList.Count > 0) { Elmah.ErrorLogEntry entry = errorList[0] as Elmah.ErrorLogEntry; Elmah.ErrorLogEntry detailedEntry = Elmah.ErrorLog.GetDefault(this.Context).GetError(entry.Id); if (sessionId == detailedEntry.Error.Cookies["ASP.NET_SessionId"]) { errorId = entry.Id; } } } ErrorIdLiteral.Text = errorId; SessionIdLiteral.Text = sessionId; DateUtcLiteral.Text = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm"); }
Here I am using the Elmah class to look up the database and get the last error. To ensure the error belongs to the the user. (I don't want just anyone browsing to the page and seeing the last error. I load up the more detailed error data via the GetError(errorId) method and search for the the recorded SessionId of that error. If the users current SessionId matches then this error was thrown for this user.
Finally I display the ErrorId, SessionId and the UtcDate. This way I should have no problems finding the exact error and any related errors for that user.
Now of course you can alter the code to display what ever details you want.
Also, getting the last error may fail if two people have an error at the same time. This is true but the session and time data will help. If it is really important for you to get an exact error Id match you could pull back, say, the last 10 and check each one for a matching session to ensure you match the error with the user.
Big Bonus
What about JavaScript errors? Wouldn't it be great to be able to capture these as well. Good news, you can!
Using Nuget, install JSNLog.Elmah Now you can have any JavaScript errors that happen on the client also logged into your Elmah database. Check out the JSNLog website for all the details on this tool.
In a nutshell you can add a global exception capture on your master page to capture any JavaScript error. You can also add try catch and logging options to your JavaScript code. I encourage you to check out the website and add it to your project as well. There are a lot of great features and options to handle and record JavaScript issues to your Elmah log.
Recall that I said to set the applicationName in the Elmah config.
This is important because when the JavaScript errors are logged, if you don't have a preset applicationName then the name is logged as an empty string. This has an impact when you view the Elmah log because the stored proc takes the applicationName as a WHERE parameter clause and thus the JavaScript errors won't show. (As Elmah will look up the IIS name as the alternative.)
The only issue I have found now is that I want errors to happen and see this all working. To this end I create a a special errors page so I can make all these errors happen and show off.
Update
An issue I didn't realise at the time was that your 404, (or 500), page did not return the correct status code. I have added a new post detailing the issue and what to do.
Thanks for reading and feel free to comment - Darrin Robertson
If I was really helpful and you would buy me a coffee if you could, yay! You can.
Make a Comment