OdeToCode IC Logo

A Message For You

Friday, December 30, 2005 by scott

Just figure out what the following program writes to the console...

using System;
using System.Collections.Generic;
using _ = System.Text.StringBuilder;
using __ = System.Console;
using ___ = System.Collections.Generic.IEnumerable<int?>;

class wtf
{
    
static void Main()
    {
        
_ _ = new _().Append(_2());
        
int i= 0; foreach(int? x in _1())
        {
            _[(
int)((x ?? i) < 0 ? i ^ 2 % 10 : x)] = _2()[i++];
        }
        
__.WriteLine(_.ToString());
    }

    
static char[] _2()
    {
        
return
        (  
          
"H"      +      "Y"
                    +
            
"E"     +     "N"
                    +
            
"A"    +    " "
                    +
              
"P"   +   "A"
                    +
              
"W"  +  " "
                    +
                
"P" + "R"
                    +
                
"E"+"Y"
        
                 ).ToCharArray();                
    }    
    
static ___ _1()
    {
        
int?[] _ = {  
                     0, 10, 11, 6,  1, 5, 3,
                    
/*~~~~~~~~~~~~~~~~~~~*/
                     12,  8,  9, 2, 13, 7, 4
                    };

      
foreach (int? x in _)
            
yield
                    return
       null
              ??
              x;
    }
}

Who is gonna port this to VB?

Profile Addendum

Thursday, December 29, 2005 by scott

There are a couple articles on the web saying you can use a base Profile class like so:

using System;
using System.Web.Profile;

public class CustomProfile : ProfileBase
{    
    
public string PetName
    {
        
get { return _petName; }
        
set { _petName = value; }
    }
    
private string _petName;      
}

Then use the inherits attribute in web.config like so:

<profile inherits="CustomProfile"/>

I hope I can save you some time by pointing out the above setup doesn’t work.

You’ll find the HttpContext contains a non-null Profile object, but SQL Profiler will confirm that ASP.NET never populates the object with data from the database. Perhaps the above approached worked in pre-RTM bits, I’m not sure.

Here is a CustomProfile that will fetch and save data properly:

using System;
using System.Web.Profile;

public class CustomProfile : ProfileBase
{    
    
public string PetName
    {
        
get { return (string)base["PetName"]; }
        
set { base["PetName"] = value; }
    }   
}

With the above class, Profiler shows the expected “exec dbo.aspnet_Profile_GetProperties” and “exec dbo.aspnet_Profile_SetProperties” commands and it all works as expected.

ASP.NET uses a lazy-load pattern for the Profile object. The runtime doesn’t touch the database until execution first reaches SettingsBase.GetPropertyValueByName (SettingsBase is the parent class of ProfileBase). GetPropertyValueByName will ask the Profile provider to fetch all properties from the database.

The good news is, if a web form doesn’t use any Profile properties, you don’t pay the overhead of a database hit.

Low Profile

Wednesday, December 28, 2005 by scott

New article on OdeToCode: Profiles In ASP.NET 2.0

One good reason to inherit from a custom base class is that we can add additional logic to the Profile object. For instance, we can add validation logic to the Age property to ensure the user provides us with a sensible age, and not a value like 443.

Read more…

Rambling about ASP.NET Themes

Wednesday, December 21, 2005 by scott

If I go to the trouble of designing multiple ASP.NET Themes using skin and css files, chances are I will let a user choose the theme they find most pleasing. To let the user choose a theme, I’d first have to know what themes are available….

Plan 1

I could maintain the list of available themes in a configuration file, or in a database table. When I add new themes, I’ll have to update the file or table. A better design would be for the application to figure out which themes are available auto-magically, perhaps by using a class provided by ASP.NET….

Plan 2

Digging around, I find the IThemeResolutionService interface and it’s GetAllThemeProviders method. Each ThemeProvider encapsulates a theme. Unfortunately, the service appears to be available only at design time.

Due to the dynamic compilation features in ASP.NET, I figure there is no foolproof way to see all available themes. Even if I reflected every Type in the AppDomain, some themes may still be left on disk and un-compiled. Themes … on disk …

Plan 3

A simple solution would just look at what directories are available underneath the App_Themes directory. Without any error checking, the code would look like:

string themesPath = Server.MapPath("~/App_Themes");

List<string> themes = new List<string>();
foreach (string d in Directory.GetDirectories(themesPath))
{
  
// strip away all but the directory name            
  themes.Add(
      d.Substring(
        d.LastIndexOf(
Path.DirectorySeparatorChar) + 1
      )
    );
}

The above code seems fine until I start thinking about how it could break. It occurs to me that the ASP.NET plumbing doesn’t look directly at the file-system, but sees files and directories through virtualization goggles. There is probably a safer way to write the code, just in case the themes live in a database, or get pre-compiled away….

Plan 4

VirtualPathProvider vPathProvider;    
vPathProvider =
HostingEnvironment.VirtualPathProvider;

VirtualDirectory themeDirectory;
themeDirectory = vPathProvider.GetDirectory(
"~/App_Themes");

List<string> themes = new List<string>();
foreach (VirtualDirectory d in themeDirectory.Directories)
{
  themes.Add(d.Name);
}

Now, I’m cooking with oil and a bottle of sherry.
Except … the rumors of the demise of global themes have been greatly exaggerated…

Plan 5

At some point in the preview builds of ASP.NET, Microsoft shipped a couple global themes. Although the themes themselves were dropped around beta 2, the ability to define global themes for all applications still exists. See the bottom of “How to: Define ASP.NET Page Themes” to setup global themes under WebDev and IIS.

Although I found it possible to get to the global themes directory using the VirtualPathProvider and HttpRuntime.AspClientScriptVirtualPath, the solution was beginning to feel a bit clunky. Also, since global themes live outside an application’s home directory, there is no way to read the available theme directories when running at medium trust. (The themes are still available to skin a page, however).

Finally, I’ve arrived at…

Plan 6

Go back to plan 1 … or plan 4.

Event Order

Monday, December 19, 2005 by scott

I was going to write a short blog post about event order when a MasterPage is present, but I found MSDN covers the topic well: “Events in ASP.NET Master and Content Pages”. Notice the MasterPage’s Init event will fire before the content page’s Init event, but the content page’s Load event fires before the MasterPage’s Load event.

Sound confusing? Well, the MSDN doc offers advice in a declarative form: “The sequence of events in master and content pages rarely is important for you as page developer”. If you find the order of events is important, you might be tightly coupling your content pages and master pages and sliding towards a type of “Master Page Anti-Pattern” design. Keep the two entities loosely coupled with custom events, and maintainability improves.

Return of the Web Project

Sunday, December 18, 2005 by scott

Do you remember the mood at the end of “The Empire Strikes Back”? Darth Vader froze Han Solo in carbonite, then proceeded to slap Luke around like a hockey puck. Just when we thought all was lost, along came “Return of the Jedi”. Luke broke out some new kung-fu moves, and Princess Leia donned a bikini (which perhaps not surprisingly, spawned this website). The galactic mood was on a definite upswing.

Similarly, the “project-less” web project in Visual Studio 2005 left little hope for some ASP.NET applications to make an easy transition to 2.0. Fortunately, ScottGu announced a preview of the Web Application Project model this week.

The preview has rough edges – but is familiar. Familiar because the project based WAP model works like the Visual Studio 2003 model. Code-behind files compile into a single assembly. The model is also familiar because it works like the Windows Forms designer model, the Windows Workflow designer model, the typed DataSet designer model, the WinFX / Avalon designer model, and so on.

I hope the final release will parse @ MasterType and @ Previous page directives, as well as profile settings, to make strongly typed properties available in code-behind. Pre-compilation with aspnet_compiler.exe appears to work against the new project type, which is good.

Will Web Application Projects make migration easier? Certainly, the mental migration from 1.1 to 2.0 will be easier. The codebase should also experience fewer hiccups. I wonder if the migration tool will attempt to separate the designer generated code and developer code living together in today’s code-behind files. Breaking up is hard to do, but keeping designer goo in a separate partial class is nice.

Death by Unhandled Exception

Wednesday, December 14, 2005 by scott

In .NET 1.1, an unhandled exception would terminate a process only if the exception occurred on the main application thread. The runtime would silently swallow unhandled exceptions on other threads to prevent termination. I’ll leave it for the reader to decide if this is behavior is naughty or nice.

In .NET 2.0, unhandled exceptions on any thread will most likely terminate the application (see Exceptions In Managed Threads for details). For ASP.NET developers taking threading into their own hands to implement a “fire and forget” strategy, it’s important to realize this change in behavior. Imagine the following code inside a web form.

protected void button1_click(object sender, EventArgs e)
{
doWork(
null);
}

protected void button2_click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(
new WaitCallback(doWork), null
);
}

protected void doWork(object state)
{
string s = state.ToString();
}

Both button event handlers ultimately call the doWork method and pass a null value as the parameter. The doWork method will always throw a NullReferenceException. The button1_click method calls doWork synchronously. Since the exception in doWork occurs on a thread processing a request, the ASP.NET runtime will catch the exception and throw up the default ASP.NET error page. One technique for globallylogging these types of exceptions is to wire upApplication_Error.

The button2_click method uses a second thread to execute doWork. The second thread is not in a request processing context, so the exception will not be seenin the Application_Error event handler. This is true for QueueUserWorkItem and when working with aThread object directly. Note: I’m not encouraging anyone to use either QueueUserWorkItem or Thread.Start in ASP.NET. I generally recommend you avoid spinning off work unless you know what you are doing. Asynchronous pages might be what you are looking for instead.

In ASP.NET 1.1, the runtime would swallow the NullReferenceExceptionfrom the second thread and the application would continue in blissful ignorance - not so in ASP.NET 2.0. Here is what happens when doWork executes on a second thread with the WebDev server.

dead webdev

This couldn’t happen with IIS, right? Well, on XP (IIS 5), you won’t see an error dialog box, but check the Application event log afterwards….

aspnet_wp.exe (PID: 316) stopped unexpectedly.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

It’s dead, Jim.

With IIS 6, w3wp.exe willgo down in flames. Iusually see two event log entries left behind. The first entry is a warning entry with healthy amounts of detail, including a stack trace if pdb files are available. The second entry is cryptic:

EventType clr20r3, P1 w3wp.exe, P2 6.0.3790.1830, P3 42435be1, P4 app_web_pion_w1z, P5 0.0.0.0, P6 439f8d74, P7 3, P8 1, P9 system.nullreferenceexception, P10 NIL.

Of course, another ASP.NET worker process will rise upimmediately afterwards, buttermination is just notin the "good thing" category.Make sure you have a try / catch in place if you are using your own threads in ASP.NET 2.0.