Thursday, March 30, 2006

Tuesday, March 28, 2006

Larry O'Brien nails it...

Microsoft is looking for a developer whose "first task will be to drive the exploration of other dynamic languages such as Ruby and JavaScript on the CLR". Dang. If only they allowed people to work remotely...

[Via Knowing.NET]

Makes you wonder how much other talent they're missing out on...

Monday, March 27, 2006

RE: Enumerating available localized language resources in .NET

The coolest thing about the blog culture is people that respond to comments (and suggested topics). One person that consistently goes above the call is Michael Kaplan. On Friday, I asked a question via his contact me link, and by 9am Saturday he had grokked my question, found a solution, and blogged about it for the universe to share. This is awesome! Microsoft is lucky to have someone of Michael's caliber, and we are lucky to have him exposed and working on weekends for us.

Unfortunately, it's not all peaches and cream in .Net land. Frankly the solution he gave is suboptimal for my use (through no fault of Michael's). Let me explain my situation, the initial solution, the problems, and the eventual resolution

My requirements

Hard requirements

  • I'm coding an ASP.Net 2.0 application
  • The application has a select for the user to choose the language they want the application to be rendered in
  • The selection is maintained in the ASP.Net Profile both for anonymous and logged-in users
  • The selected language is used to force the servicing thread's CurrentCulture and CurrentUICulture

Soft requirements

  • We want to allow the setup user to define the locales they want in the list
  • We want to default the list to all installed translations available to based on the installed .Net runtime language packs.

So, first step is to enumerate the CultureInfo objects like this (using a custom business object called Locale and WilsonORMapper)

public static void Fill()
{
    ObjectSpace space = Manager.ObjectSpace.IsolatedContext;
 
    using (Transaction transaction = space.BeginTransaction(IsolationLevel.ReadCommitted))
    {
        Dictionary<string, Locale> locales = new Dictionary<string, Locale>();
 
        foreach (Locale existingLocale in space.GetCollection<Locale>(string.Empty))
        {
            locales.Add(existingLocale.LocaleCode, existingLocale);
        }
 
        // Grab a type that we know is in mscorlib
         Assembly mscorlib = Assembly.GetAssembly(typeof(System.Object));
 
        // Enumerate through all the languages .NET may be localized into
        foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
        {
            string name = ci.Name;

            if ( ! locales.ContainsKey(name))
            {
               Locale newLocale = new Locale();
               newLocale.LocaleCode = name;
               newLocale.Description = ci.NativeName;
               newLocale.InvariantDescription = ci.DisplayName;
               space.StartTracking(newLocale, InitialState.Inserted);
               locales.Add(name, newLocale);
            }
        }
 
        transaction.PersistChanges(locales.Values, PersistDepth.ObjectGraph);
        transaction.Commit();
    }
}

This is pretty simple. The problem is that I end up with more than 159 specific cultures (and 69 neutral cultures) available, only a few of which have localized .Net runtimes installed. This is where I searched and eventually punted to Micheal. He suggested that I can use the Assembly.GetSatelliteAssembly(CultureInfo ci) method to feel out the correct culture. He also changed his call to CultureInfo.GetCultures() to also include the CultureTypes.NeutralCultures enumeration flag.

Several problems with this approach pop up. Firstly, since we are calling GetSatelliteAssembly, it has to have a way to communicate when the assembly cannot be located. It does this by throwing a FileNotFoundException. That sucks because I'm going to get hundreds of exceptions for the 24 or so available localizations, and exceptions are a terrible performance hit (not to mention bad API design). This could be easily cured if Microsoft had exposed the (Reflectored) InternalGetSatelliteAssembly method, which takes a boolean flag to determine if it should throwOnFileNotFound. Alas, that isn't exposed and I certainly am not going to start twiddling the internal members of mscorlib. Another possibility is the tantalizing ResourceManager.TryLookingForSatellite(), but it's private as well.

The other problem with this is the fact that I am getting back neutral cultures. These are great when doing localizations because you can create an English translation without worrying about the differences between British English and United States English. This sucks because you cannot set the Thread.CurrentUICulture to any neutral culture (it wants you to be specific, and it'll thunk down to the neutral culture if needed). Thus I've got a tiny list of cultures of which only a couple are actually useable as-is.

Sigh... what to do? I take my hint from the path Michael has started down and do some more reflectoring. So after much digging, I find that the best I can do is catch is basically what Michael suggests, so I tried to minimize the number of exceptions thrown and solve the neutral/specific culture issue, so what I did was track the list of known installed neutral localizations and test the parents of each specific culture. The final code loos likes this:

// Grab a type that we know is in mscorlib
private static readonly Assembly s_MSCorLib = Assembly.GetAssembly(typeof(System.Object));
 
public static void Fill()
{
    ObjectSpace space = Manager.ObjectSpace.IsolatedContext;
 
    using (Transaction transaction = space.BeginTransaction(IsolationLevel.ReadCommitted))
    {
        Dictionary<string, Locale> locales = new Dictionary<string, Locale>();
 
        foreach (Locale existingLocale in space.GetCollection<Locale>(string.Empty))
        {
            locales.Add(existingLocale.LocaleCode, existingLocale);
        }
 
        Dictionary<string, CultureInfo> neutralCultures = new Dictionary<string, CultureInfo>();
 
        // Enumerate through all the neutral cultures first (this saves time in checking all the others...)
        foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.NeutralCultures))
        {
            if (AddIt(locales, neutralCultures, ci))
            {
                neutralCultures.Add(ci.Name, ci);
            }
        }
 
        // Enumerate through all the specific languages .NET may be localized into
        foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.SpecificCultures))
        {
            if (AddIt(locales, neutralCultures, ci))
            {
                // if we got here, the culture localizations are installed.
                Locale newLocale = new Locale();
                newLocale.LocaleCode = ci.Name;
                newLocale.Description = ci.NativeName;
                newLocale.InvariantDescription = ci.DisplayName;
                space.StartTracking(newLocale, InitialState.Inserted);
                locales.Add(ci.Name, newLocale);
            }
        }
 
        transaction.PersistChanges(locales.Values, PersistDepth.ObjectGraph);
        transaction.Commit();
    }
}
 
private static bool AddIt(Dictionary<string, Locale> locales
    , Dictionary<string, CultureInfo> neutralCultures
    , CultureInfo ci)
{
    bool addIt = false;
 
    if (!locales.ContainsKey(ci.Name))
    {
        // if it exists in neutral form, we can use it, special-case English since that is
        // always installed..
        if (neutralCultures.ContainsKey(ci.Name) || ci.TwoLetterISOLanguageName == "en")
        {
            addIt = true;
        }
        else
        {
            try
            {
                Assembly satellite = s_MSCorLib.GetSatelliteAssembly(ci);
                addIt = true;   // if we got here, the localization was found...
            }
            catch (FileNotFoundException)
            {
                if (ci.Parent != null && ci.Parent != CultureInfo.InvariantCulture)
                    addIt = AddIt(ref locales, ref neutralCultures, ci.Parent);
            }
        }
    }
 
    return addIt;
}

Thursday, March 23, 2006

RE: Viewing SecurityExceptions

Shawn Farkas notes here that you can't display some of the information available in SecurityException when not running in fully trusted code (like an ASP.Net host). Then along comes Dominick Baier with the fix. I smell a setup.

In any case, the trick is to put a fully-trusted assembly (in the GAC, of course!) to handle the extraction of this information, and pass the unmangled SecurityException in. If the trusted assembly's method asserts the needed ControlEvidence and ControlPolicy then you can get the extra goodies in ToString().
Cool!

using System;
using System.Security.Permissions;
using System.Security;

[assembly: AllowPartiallyTrustedCallers]
namespace LeastPrivilege
{
  [
   SecurityPermission(SecurityAction.Assert, 
   ControlEvidence=true, ControlPolicy=true)
  ]

  public class SecurityExceptionViewer
  {
    public static string[] ViewException(SecurityException ex)
    {
      return new string[] {
        ex.ToString(),
        ex.Demanded.ToString(),
        ex.GrantedSet.ToString() 
      };
    }
  }
}
[Via www.leastprivilege.com]

Wednesday, March 22, 2006

Introducing CSS event:Selectors

Justin Palmer has done it again! Simplify the whole process of linking up behavior using CSS selectors. This time, though, support for events are included. This is like behavior.js with even more code-refactoring, integrated with prototype.js to make the simple things simple. Head on over! Introducing CSS event:Selectors

Wednesday, March 15, 2006

Old Skool Trippin', Part I

This is simply amazing... being an old BBS sysop, I would have KILLED for this ability in the day... Old Skool Trippin', Part I

Tuesday, March 14, 2006

PHLAT - Intuitive Personal Search

PHLAT is PHAT! I may finally have to switch from Google Desktop Search to Windows Desktop Search, because PHLAT lets you tag your files just like GMail lets you tag your messages (why Google couldn't figure this out, I don't know). The search/filter strategy is also really cool, letting you "refine" your search while clearly seeing the filters being applied. PHLAT - Intuitive Personal Search

Visual Studio 2005 sometimes seems slow starting up.

I've found that occasionally we're seeing VS startup times being very slow. In one recent case, it was the MRU files/folders (that list that shows when the File / Recent Files or File / Recent Projects has references to the files or folders that are no longer reachable (say on a disconnected network drive). All you have to do is delete the HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\FileMRUList or HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\ProjectMRUList registry keys.

Another couple of hints come from Tim Noonan

If you have start-up speed issues when the Team Foundation Server is not accessible, you can add a DWORD value called AutoLoadServer under HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\TeamFoundatio and set the value to 0. That way VS won't try to connect to the TFS machine on startup. You can always set the value to 1 to enable auto-reconnect. I've personally used this trick while doing the maintenance on my TFS server.

The final trick is best described by the hippie coder himself as I've never used it

Don't Automatically Get Missing Files

The next setting is to control whether or not we attempt to automatically get files from the Team Foundation Server that are missing from a solution or project.  This can be very helpful when you have files that are part of a project or solution but are generated by the build process as well as situations where you are working offline.

Add a DWORD value called DisableGetMissingFiles under HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl or HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\TeamFoundation\SourceControl. Any value other than zero will disable getting files from the server automatically.

[Via Rants of a hippie coder]

Thursday, March 09, 2006

Extending dynamic sorting of objects using lightweight code generation.

UPDATE: This project is hosted on CodePlex as the Dynamic Reflection Library for all further updates.
RE: Dynamic sorting of objects using lightweight code generation.

I got a report of issues sorting objects that have null (not Nullable<T>) values. In the previous implementation, I blindly call down to the CompareTo method associated with the property's Type. This obviously doesn't work with virtual methods. For some types (like String) it was enough to simply make the emitted code a Call instead of a CallVirt, but I didn't want to be half-right. So the new version does the right thing and handles the null-checks for the left and right filed/property values.

The emitted code is slightly more complex, but still about as fast as I now check for IsFinal methods and use Call instead of CallVirt when possible. This optimization is in place for property get calls, and the call the CompareTo, so it is a bit faster.

I also fixed an issue if you compared a whole bunch of attributes and blew the generate IL so large that the loop-break label wasn't reachable by a short branch (nobody reported this, just noticed in code review).

I've updated both the DynamicSorter.zip file and the Utilites.zip file (the latter has other updates to improve fxCop compliance).

The emitted code for "FirstName, lastName, Gender" [String property, double field, Enum property, respectively] now looks like this:

IL_0000:  ldarg.0 
IL_0001:  callvirt   System.String get_FirstName()/DynamicComparerSample.Person
IL_0006:  dup     
IL_0007:  brtrue.s   IL_0018
IL_0009:  pop     
IL_000a:  ldarg.1 
IL_000b:  callvirt   System.String get_FirstName()/DynamicComparerSample.Person
IL_0010:  brtrue.s   IL_0015
IL_0012:  ldc.i4.0
IL_0013:  br.s       IL_0023
IL_0015:  ldc.i4.m1
IL_0016:  br.s       IL_0023
IL_0018:  ldarg.1 
IL_0019:  callvirt   System.String get_FirstName()/DynamicComparerSample.Person
IL_001e:  call       Int32 CompareTo(System.String)/System.String
IL_0023:  dup     
IL_0024:  brtrue     IL_0060
IL_0029:  pop     
IL_002a:  ldarg.0 
IL_002b:  ldfld      Double age/DynamicComparerSample.Person
IL_0030:  stloc.0 
IL_0031:  ldloca.s   V_0
IL_0033:  ldarg.1 
IL_0034:  ldfld      Double age/DynamicComparerSample.Person
IL_0039:  call       Int32 CompareTo(Double)/System.Double
IL_003e:  dup     
IL_003f:  brtrue     IL_0060
IL_0044:  pop     
IL_0045:  ldarg.0 
IL_0046:  callvirt   DynamicComparerSample.Gender get_Gender()/DynamicComparerSample.Person
IL_004b:  box        DynamicComparerSample.Gender
IL_0050:  ldarg.1 
IL_0051:  callvirt   DynamicComparerSample.Gender get_Gender()/DynamicComparerSample.Person
IL_0056:  box        DynamicComparerSample.Gender
IL_005b:  call       Int32 CompareTo(System.Object)/System.Enum
IL_0060:  ret

Tuesday, March 07, 2006

Sometimes it takes forever to sink in... Boolean flags are not readable.

So today I was looking at some code and some advice I read more than a year ago finally clicked. Quick, what does this code mean?

Provider provider = Enum.Parse(typeof(Provider), mySettings.ProviderName, true);

Back from looking up that overload of Enum.Parse yet? So my point is this, you don't know what that true means without looking it up. How much more obvious is this code?

enum
{
   CaseSensitive = 0
   , IgnoreCase = 1
} MatchCase;
Provider provider = Enum.Parse(typeof(Provider), mySettings.ProviderName, IgnoreCase);

The moral of this story is that instead of building methods that take boolean flags, construct them to take enumerations that clearly document the meaning of the flag. This way you don't have to do an API lookup, or just know. I first saw this idea about a year ago... if my head wasn't so full of things I had to just know it might have sunk in earlier.

Friday, March 03, 2006

Are they ever going to make ObjectDataSource worthwhile?

The current "stateless" design of ObjectDataSource when using the DataObjectTypeName is not viable for use in modern Ajax-driven applications. Since the ObjectDataSource creates a new object on every postback, there is no way to persist between call-backs any changes made by other previous postbacks without committing the infomation to the datastore.

Previously proposed "solutions" like not using DataObjectTypes are unacceptible due to the coupling of the view to the needed Insert/Update/Delete methods.

Microsoft should allow ObjectDataSource to have "acquire" symantics when handling postbacks, so that it can get the object instance being manipulated from the session store (whereever that is). Then ObjectDataSource/GridView/etc. can set the just properties bound on that specific view.

The easiest implementation would be to call an Acquire method (parameterized with the declare key values) and use that object. This would be an overridable method that could in the base implementation simply do the current behavior of an ex-nilo call to the DataObjectType's default constructor. In derived classes, we can offer whatever symantics we need (including cache, session, ORM, or web-service based object acquisition).

Of course we could do this ourselves using a derived ObjectDataSource and ObjectDataSourceView, except they didn't do the implementation of that correctly in the framework. ObjectDataSource never calls the derived-classes GetView(String) method. If they simply fixed that so that ObjectDataSource's GetView() method called GetView(String.Empty), I could do it myself.

If you agree, feel free to vote on MSDN Product Feedback Center

Wednesday, March 01, 2006

System.Enum.CompareTo needs boxed values...

I got a report of a bug in the DynamicComparer sources when comparing Enum values. Turns out that you need to box the values before you call System.Enum.CompareTo().

I've made the necessary changes in both the DynamicComparer.zip and Utilities.zip files.

RE: Dynamic sorting of objects using lightweight code generation.