Well, I thought I had the hard parts worked out.
In my previous post I threw out this little bit of hand-waving:
The hard part of this was not figuring out how to put 5 environments’ worth of values into a single web.config – that’s already a solved problem in my shop (and I’m sure you could come up with your own approach). The hard part here was figuring out to programmatically override the values that I would normally put into a web.config.
Well, as it turns out, changing out the values at runtime was the hard part here. In fact, when it came to configuring ELMAH to generate email notifications, it was nearly insurmountable.
The mechanisms that we’ve developed to handle a "dynamic" web.config are all centered on the assumption that we can do the following (another gem I wrote in the last post):
At runtime, figure out the URL that the site is being executed under, and look up the values for that URL in the web.config.
And here’s where things fell apart. You see,
- In order to figure out the current URL, I needed a Request object to inspect.
- The soonest I could get at the Request was the Application’s Begin_Request event.
- The ELMAH module, however, was getting initialized before Application.Start, let alone Begin_Request.
- Once initialized, ELMAH cached the now-incorrect settings, and provides no mechanism to force an update after the fact.
If I can’t override the settings when the application first starts up, how about forcing an override when the first error occurs? There was an ELMAH event for that. Unfortunately, being a module, the ELMAH email module ends up processing the error message AFTER the Request has already been processed, so it’s no longer available.
Rather than trying to come up with an even more elaborate (aka "convoluted") mechanism for saving off the current environment and then overriding ELMAH’s cache when the time came, I considered modifying the ELMAH source to initialize later, have a way to update the cached settings, etc. Unfortunately, I had already sunk too much time trying to get this to work, so I opted for a much simpler solution – a simple email method of my own device, invoked in the Application.Error handler. It functions, and occurs while the Request is still available, so I had a much easier time wiring it into the one web.config structure I had in place. It’s not as robust as ELMAH, and certainly isn’t the way I wanted to go with it. After using ELMAH for dozens of sites over the last few years, I had come to rely on it so completely. It felt quite odd to have to go without it.
The good news is that the ELMAH mail module was the only one to give me trouble. The logging module CAN be updated on Application.Begin_Request. Although, even there I went a different route. Instead of logging the errors to the file system, I opted to use the In-Memory provider. Here is my revised web.config (only the ELMAH-specific bits are shown):
<?xml version="1.0"?> <configuration> <configSections> <!-- Error Logging Modules and Handlers (ELMAH) Copyright (c) 2004-7, Atif Aziz. All rights reserved. --> <sectionGroup name="elmah"> <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"/> <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"/> <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah"/> </sectionGroup> </configSections> <system.web> <httpHandlers> <add verb="POST,GET,HEAD" path="errors/report.axd" type="Elmah.ErrorLogPageFactory, Elmah"/> </httpHandlers> <httpModules> <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/> <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/> </httpModules> </system.web> <elmah> <errorLog type="Elmah.MemoryErrorLog, Elmah" /> <security allowRemoteAccess="yes"/> <errorFilter> <test> <equal binding="HttpStatusCode" value="404" type="Int32"/> </test> </errorFilter> </elmah> <system.webServer> <modules runAllManagedModulesForAllRequests="true"> <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah"/> <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah"/> </modules> <handlers> <add name="ElmahErrorReportingPage" verb="POST,GET,HEAD" path="errors/report.axd" type="Elmah.ErrorLogPageFactory, Elmah"/> </handlers> </system.webServer> </configuration>
Since there was no longer any difference between the environments, I didn’t have to override anything.
The ActiveRecord initialization also seemed to go in as I expected. Both ELMAH logging and ActiveRecord have been in place and working in our environments for a solid week now.
So lesson learned – get the code actually working before I blog on it.