Sunday, May 03, 2009

As you may or may not be aware IE 7+ (and other browsers) support using CSS 2.1 attribute selectors.  Attribute selectors allow you to specify a style on an element whose element matches a particular pattern.  For instance:

td[x] { font-weight:bold; }

This selector will bold the text of any TD element on the page that has an attribute "x" (regardless of value).  Other attribute selector styles include:

...[x="value"] matches where x is exactly 'value'
...[x~="value"] matches where x contains a space-separated list of values, one of which is exactly 'value'
...[x^="value"] matches where the attribute x begins with 'value'
...[x$="value"] matches where the attribute x ends with 'value'
...[x*="value"] matches where the attribute x contains 'value'
...[x|="value"] matches where the attribute x begins with either 'value' or 'value-'

In and of themselves, these are pretty darn cool.  You can do some neat things with CSS and HTML.

I ran into an interested scenario this past week that I'd like to share.  I have some JavaScript that alters the value of attributes at runtime.  I found that the page doesn't automatically update according to the stylesheet specification.  Curiously, it would update when I moused-over the element in question.

I found, however, that I could force the issue by assigning the CSS classname to the element that it already has.  This is enough to trigger the change and have the element update according to its attribute values.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <style type="text/css">
      td[req] { font-weight:bold; }
      td[err="1"] { color:red !important; }
   </style>
   <script type="text/javascript">
      function fn() {
         var td = document.getElementById("t");
         td.err = "0";
         td.className = td.className;  // trigger the change by reassigning the CSS class
      }
   </script>
</head>
<body>
   <table>
      <tr><td class="test" err="1" req="1" id="t">Cell</td></tr>
   </table>
   <button onclick="javascript:fn();">Remove Error</button>
</body>
</html>

Interestingly, for this to work properly in a non-IE browser (such as Firefox), I found I could not use the object.property=value syntax.  Instead, I had to use the SetAttribute() function.  Also, with Firefox, reassigning the className was unnecessary.

td.setAttribute("err", "0");

That's probably the best way to handle it then, for cross-browser compliance.

CSS | Web
Sunday, May 03, 2009 9:28:44 PM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback
 Tuesday, November 04, 2008

This evening, I upgraded an installation of SQL Server Express 2005 to its 2008 counterpart because I have a few small websites that I've developed that rely on SQL User Instances.  The upgrade was smooth enough, but I was promptly and unexpectedly greeted with an error message that I had seen before:

Failed to generate a user instance of SQL Server due to a failure in starting the process for the user instance. The connection will be closed.

Well, in the past to address this issue I've had to 1) stop the SQL Express service, 2) delete the user's SQLEXPRESS directory, and 3) restart the service.  The user, in this case, is the account under which the User Instance is be created.

For instance, supposing that my user name were USERNAME, this directory would be C:\Documents and Settings\USERNAME\Local Settings\Application Data\Microsoft\Microsoft SQL Server Data\SQLEXPRESS.  Note, that on a Vista/Windows Server 2008 machine that path is addressable (due to some very ingenious reparse point (a.k.a. junction) mappings, the actual path is C:\Users\USERNAME\AppData\Local\Microsoft\Microsoft SQL Server Data\SQLEXPRESS.

In this particular instance, however, this wasn't working for me.  The reason was immediately obvious: my SQL Express service runs under the NETWORK SERVICE account, not USERNAME.  I verified that I could indeed host the User Instance by explicitly impersonating a priviliged user in the web.config file:

<identity impersonate="true" userName="MACHINE\Account" password="somethingSecure" />

However, when I set it back to not impersonate (as it was before the upgrade to SQL Server 2008) the User Instance would not start.

Ultimately, I did have to delete Network Service's SQLEXPRESS directory, but it's found in a completely different location:  %WINDIR%\ServiceProfiles\NetworkService\AppData\Local\Microsoft\Microsoft SQL Server Data\SQLEXPRESS.  Once I took care of that directory and restarted the SQL Services my site was off to the races.

Tuesday, November 04, 2008 4:00:03 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [4]  |  Trackback
 Saturday, June 14, 2008

I was experiencing a most perplexing issue yesterday and the day prior that I *finally* resolved late last night.  In essence, I have a Windows Service that I wrote that hosts an adapted Cassini server which, in turn, hosts an ASP.NET website product running on the client's computer.  The issue was the classic case of 'it works on my machine'.

I could install my service, configure it, and run an ASP.NET website on my computer.  It was a beautiful and exciting thing to witness.  When I went to execute it on another computer, however, the service would start up perfectly but when I made the first request to the website the service would crash (immediately stop) and log an error to the system's Application event log.  The error was a FileNotFoundException.

After troubleshooting the obvious stuff for a bit (folder permissions (the Service runs as NETWORK SERVICE), executables in the proper places, comparing systems, etc), I resorted to more drastic means. :)  Actually, this is technique I frequently employ when troubleshooting issues loading assemblies.

You can troubleshoot Fusion binding issues by simply setting up some registry keys on the machine in question.  Fusion, in short, is the name given by Microsoft for their assembly loading and binding technology in .NET.  Because I don't have the .NET SDK or Visual Studio on the target machine in question, I had to set it up manually.  I added the following registry keys:

HKLM\SOFTWARE\Microsoft\Fusion\LogPath (string) with value pointing to my desired output folder (C:\(TEMP)\FusionLog).
HKLM\SOFTWARE\Microsoft\Fusion\LogFailures (DWORD) with a value of 1.

I restarted the service and made another web request to witness the failure for the umpteenth time.  Sure enough I got some output in my FusionLog folder:

*** Assembly Binder Log Entry  (6/13/2008 @ 9:18:40 PM) ***

The operation failed.
Bind result: hr = 0x80070002. The system cannot find the file specified.

Assembly manager loaded from:  C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorwks.dll
Running under executable  C:\Program Files\Devstone\WebHostSvc\WebHostSvc.exe
--- A detailed error log follows.

=== Pre-bind state information ===
LOG: User = NT AUTHORITY\NETWORK SERVICE
LOG: DisplayName = WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx
 (Fully-specified)
LOG: Appbase = file:///C:/Documents and Settings/All Users/Application Data/Devstone/Web/
LOG: Initial PrivatePath = NULL
LOG: Dynamic Base = NULL
LOG: Cache Base = NULL
LOG: AppName = f9861834
Calling assembly : (Unknown).
===
LOG: This bind starts in default load context.
LOG: Using application configuration file: C:\Documents and Settings\All Users\Application Data\Devstone\Web\web.config
LOG: Using machine configuration file from C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\config\machine.config.
LOG: Post-policy reference: WebHostSvc, Version=1.0.0.0, Culture=neutral, PublicKeyToken=xxxxxxxxxxxxxxxx
LOG: GAC Lookup was unsuccessful.
ERR: No codebases found to download from.
ERR: Unrecoverable error occurred during pre-download check (hr = 0x80070002).

The thing that immeidately stood out was the fact that the Initial PrivatePath, Dynamic Base, and Cache Base were all NULL.  On my development machine these all referenced the /Web/bin folder.  Why wasn't it looking in the \bin folder?

At this point I began debugging the service, actually stepping into the .NET Framework code itself to see if I could figure out the issue.  Delving into the HttpRuntime.cs file I could see that the \bin folder gets appended to the app domain's private paths in the InitFusion() method, just as I had expected.  However, somehow the code wasn't getting that far.  Unfortunately, I couldn't step through some of the code leading up to this point so it was difficult to see exactly why it wasn't getting here.  Actually, in retrospect, I should have looked at the code a little longer - the answer was right in front of me.

I noticed from the fusion log that it was performing a GAC Lookup for the assembly.  Now the assembly isn't GAC-registered on my development machine but I was at my wit's end with the issue.  So I GAC'd it:

gacutil /i WebHostSvc.exe

Immediately upon the next request I got the error I was looking for in the browser:

The current identity (NT AUTHORITY\NETWORK SERVICE) does not have write access to 'C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files'.

Aha!  The one thing I didn't check but should have before.  Granting NETWORK SERVICE rights to that folder fixed the problem straight away.

I then removed it from the GAC and removed the Temporary ASP.NET Files that were created, restarted the service, and ran it again to verify that it was running properly.

At this point I went back to the source code and sure enough I saw the line I was looking for.  When initializing, it invokes a method called SetUpCodegenDirectory (which maps to the Temporary ASP.NET Files folder).  This method ensures that the caller has write access to it.

So the moral of the story: make sure that the user account that you're using for ASP.NET has write access to the Temporary ASP.NET Files folder and you'll be a much happier developer.

.NET | Web
Saturday, June 14, 2008 9:42:17 AM (Mountain Standard Time, UTC-07:00)  #    Disclaimer  |  Comments [1]  |  Trackback