Using a run-time defined connection string with Enterprise Library's Data Access Block.

As you can probably tell from previous posts, I'm feel pretty mixed about on the Microsoft Patterns & Practices Enterprise Library Data Access Block (MPAPELDAB, for short).  I like the design of the classes and I really like that you can access databases other than just SQL Server but I'm having some serious problems with registry and event log access exceptions. 

A few days ago, I was trying to access a database using a connection string that I define at run-time.  I double dog dare ya to try to figure that one out.  Wow.  It is not obvious.  Far far extra far from obvious.  The MPAPELDAB appears to be written with the assumptions that...

  1. you will always know your database connection strings at deploy time
  2. they will be in your MPAPELDAB database configuration file

Not bad as assumptions go.  I can see how they would make them cuz probably 99% of the time that's the way it'll roll. 

I found the solution on “Yang's .NET Zone”.  It's non-trivial.  I took the source code, tweeked it a little, and posted my version here.  (NOTE: yes, I know you shouldn't put more than one class or enum per file...I did it so that I only had to post one file instead of 3.)

-Ben

The finer points of ASP.NET authentication and some other things I learned today.

I've been involved with ASP.NET development pretty much since it was in beta and I thought that I really knew what was going on and could cut code pretty well.  I must have gotten rusty or something cuz I feel like I've learned a TON in the last month.  Usually the knowledge is aquired in spurts and usually on a day that tries a man's (or woman's) soul.  Today was one of those days.

Four things I learned today:

  1. If your web.config “authentication mode” (< authentication mode="Forms" >) is set to “Forms“ and you want to access the user's Windows username through NTLM, you are out of luck.  No amount of futzing with the IIS directory settings or “< identity impersonate="true" / >“ will get you what you need.  You have to pick either mode=“Forms“ or mode=“Windows“.  What I wanted to do was to have my ASP.NET security first try to auth using the WindowsIdentity and if that isn't available fall back to Forms authentication.  It looks like that automatic authorization without an IE login prompt won't be possible. 
  2. ASP.NET runs as “NETWORK SERVICE“ on Windows 2003 and not ASPNET.  (read up)
  3. If the server that is running your ASP.NET application has a dot (aka “a period“, “.“) in the address, Internet Explorer won't attempt to negotiate NTLM unless you've added that site to IE's local intranet zone.  If there's a dot, IE assumes that you're hitting an internet site rather than an intranet set.  (read up)
  4. The Microsoft Patterns & Practices Enterprise Library Data Access Block (MPAPELDAB, for short) is trying to do something with the Event Log and/or the registry that it absolutely doesn't have permissions to do.  Whenever there's a data access problem (SqlException, etc), it tries to log the exception -- fails -- then everything comes crashing down like a load of extra-heavy, jet-powered bricks.  This blog post has been somewhat helpful....especially the comment that suggested going into the source code and editing out all the calls that try to write to the event log.  There's also another FAQ.  And this post, too.  Alas, the problem is still not solved.  I've tried running ASP.NET as an administrator.  I've tried running “installutil“ over the MPAPELDAB dlls.  (See exception/stack trace below)

If anyone has any ideas on item #1 or item #4, please let me know.  Definitely let me know if you think I'm misunderstanding something about the security stuff. 

Major thanks to Mike Miller for helping me research and test out the Windows/Forms security stuff.  (He's definitely one of the smartest guys I know.)

-Ben

 

Here's the exception:
[Win32Exception (0x80004005): Access is denied]

[InvalidOperationException: Cannot open log for source {0}. You may not have write access.]
   System.Diagnostics.EventLog.OpenForWrite() +363
   System.Diagnostics.EventLog.WriteEvent(Int32 eventID, Int16 category, EventLogEntryType type, String[] strings, Byte[] rawData) +280
   System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category, Byte[] rawData) +462
   System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID, Int16 category) +21
   System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type, Int32 eventID) +15
   System.Diagnostics.EventLog.WriteEntry(String message, EventLogEntryType type) +11
   Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.PerformanceCounterInstances.ReportCounterFailure(String message)

[InvalidOperationException: Exception in ReportCounterFailure("Failed to create instances of performance counter '# of Command Failures/Sec' - Couldn't get process information from remote machine..".  Here's the debugging trace...Start;new EventLog('Application', '.', 'Enterprise Library Instrumentation');]
   Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.PerformanceCounterInstances.ReportCounterFailure(String message)
   Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.PerformanceCounterInstances..ctor(String categoryName, String counterName, Boolean createNewInstance)
   Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.InstrumentedEvent.AddPerformanceCounter(String category, String[] counterNames, Boolean createNewInstance)
   Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.InstrumentedEvent.Initialize(String counterCategory, String[] counterNames, Boolean createNewInstance, String eventLogSource, EventLogIdentifier[] eventIds)
   Microsoft.Practices.EnterpriseLibrary.Common.Instrumentation.InstrumentedEvent..ctor(String counterCategory, String[] counterNames, Boolean createNewInstance)
   Microsoft.Practices.EnterpriseLibrary.Data.Instrumentation.DataServiceEvent..ctor(String[] counterNames)
   Microsoft.Practices.EnterpriseLibrary.Data.Instrumentation.DataCommandFailedEvent..ctor(String[] counterNames)
   Microsoft.Practices.EnterpriseLibrary.Data.Instrumentation.DataCommandFailedEvent..cctor()

[TypeInitializationException: The type initializer for "Microsoft.Practices.EnterpriseLibrary.Data.Instrumentation.DataCommandFailedEvent" threw an exception.]
   Com.Benday.Search.SearchDA.Execute(SearchDefinition search)
   Com.Benday.Search.SearchManager.Execute(SearchDefinition search)
   Timesheet.WebUI.Global.InitializeValues(String searchName, String storeAsKey) in c:\inetpub\wwwroot\Timesheet.WebUI\Global.asax.cs:50
   Timesheet.WebUI.Global.InitializeLookupValues() in c:\inetpub\wwwroot\Timesheet.WebUI\Global.asax.cs:30
   Timesheet.WebUI.Global.Application_BeginRequest(Object sender, EventArgs e) in c:\inetpub\wwwroot\Timesheet.WebUI\Global.asax.cs:79
   System.Web.SyncEventExecutionStep.System.Web.HttpApplication+IExecutionStep.Execute() +60
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +87


 

"ASPNET" vs. "NETWORK SERVICE"

Well, this just wasted the better part of 2 hours.  I'm trying to lock down an ASP.NET app that i'm writing to “least privalege”.  I've got the ASPNET and IUSR_ permissions set to “read”, “execute” and “list”.  I kept getting “Server cannot access application directory 'c:\inetpub\blah\blahblahblah\'.  The directory does not exist or is not accessible because of security settings.” 

And i just keep beating my head against this error message for like hours.  If I enable “everyone” then everything's great.  Take it away and error.  Then I started getting a little tickle in the back of my head suggesting giving rights to “NETWORK SERVICE”.  I do that and everything's golden. 

The moral of the story is “ASPX runs as 'NETWORK SERVICE' on Windows 2003.” 

As I used to say back in back in 3rd grade....“durt durt durrrrrrrrr.“

-Ben