CTICoder

A random spillage of programming (and other) thoughts

Archive for October, 2009

A Generic run-time LINQ-based multi-level object sorter

Posted by Michael Bray on October 26, 2009

Assume you have a list of objects that has a set of properties.  These properties are stored in a StringCollection or other similar lookup, and you want to sort the objects based on some of these properties, but you don’t know at compile-time which properties to sort on or in what order (that information will be supplied at run-time, perhaps in configuration).  How do you sort this list, in a manner that honors ascending / descending as well as multi-level sorting rules?  You can’t simply sort the list by each property, since each time you sort, it will wipe out the previous sorting operation.  Of course, LINQ provides sorting thru OrderBy(…) and ThenBy(…) functions that handle the multi-level sort issue.  But it’s a bit more complicated than that, since you don’t know the properties you want to sort on.

Here, I demonstrate a relatively simple generic object sorter that correctly handles multi-level sorting and ascending/descending at each level.

private IEnumerable<T> MultiLevelSort<T, SK>(IEnumerable<T> list, List<SK> sortKeys, Func<T, SK, string> keySelector, Func<SK, bool> ascendingSelector)
{
    if (sortKeys.Count == 0) return list;

    IOrderedEnumerable<T> res = null;
    for (int i = 0; i < sortKeys.Count; i++)
    {
        SK sk = sortKeys[i];
        bool ascending = ascendingSelector(sk);
        if (i == 0)
        {
            if (ascending) res = list.OrderBy(r => keySelector(r, sk));
            else res = list.OrderByDescending(r => keySelector(r, sk));
        }
        else
        {
            if (ascending) res = res.ThenBy(r => keySelector(r, sk));
            else res = res.ThenByDescending(r => keySelector(r, sk));
        }
    }
    return res;
}

This function takes 4 parameters:

  1. An IEnumerable<T> of objects to sort
  2. A List<SK> of objects that contain sorting order information (note that this list itself is expected to already be in the correct sort order)
  3. A Func<T, SK, string> to extract the value from T based on information in SK to actually sort on
  4. A Func<SK, bool> to extract the ascending/descending information from SK

…and it returns the list correctly sorted as an IEnumerable<T>.  Note that the actual object returned is actually an IOrderedEnumerable<T> as long as there is at least one valid sort key.

This code could then be used as such:

List<MyProperty> sortProps = AllProperties.Where(sp => sp.Sort != string.Empty).OrderBy(sp => sp.SortOrder).ToList();
IEnumerable<MyObject> sortedResults = MultiLevelSort<MyObject, MyProperty>(
    results, sortProps,
    (r, pe) => r.Properties.ContainsKey(pe.Name) ? r.Properties[pe.Name] : string.Empty,
    pe => pe.Sort == "Ascending"
        ).ToList();

Where MyObject is an object that contains a StringCollection called ‘Properties’, and MyProperty is an object that contains properties called ‘Sort’ (“Ascending/Descending”), ‘SortOrder’ (an integer), and ‘Name’ (the name of the property within the MyObject.Properties collection that we want to sort on).

Advertisements

Posted in .NET | Tagged: | Leave a Comment »

Simulating VS.net’s MSI InstallURL property with WiX

Posted by Michael Bray on October 24, 2009

I recently converted several installers from VS.net to WiX.  In one of those installers, I was using a Registry Search condition to check to see if MSXML6 was installed, since the application requires it.  If it wasn’t installed, I was using Visual Studio’s InstallURL property to redirect the user to the Microsoft download page for the package so they could download and install it.

WiX doesn’t appear to have an InstallURL property available by default, but you can simulate it with some custom actions.   Along the way I learned quite a bit about how WiX structures CustomActions, and experienced quite a bit of frustration getting it to work.  The first step to simulating the InstallURL capability is to set up a Property that searches the registry for the MSXML key:

<Property Id="MSXML6">
    <RegistrySearch Id="MSXML6Search" Root="HKCR" Key="Msxml2.DOMDocument.6.0" Type="raw" /
</Property>

This code is very standard code for polling a registry value – no surprises here.  The next step is to build in two custom actions that both tie to this property:

<Property Id="cmd" Value="cmd.exe" />
<CustomAction Id="OpenMSXML6Download" Property="cmd"
    ExeCommand="/c start http://www.microsoft.com/downloads/details.aspx?FamilyID=993c0bcf-3bcf-4009-be21-27e85e1857b1"
    Execute="oncePerProcess"
    Return="check" />
<CustomAction Id="OpenMSXML6DownloadError" Error="MSXML6 must be installed first." />

The first Custom Action executes a command window, and starts the URL in the ExeCommand.  The important and confusing thing to note here is that the actual command to execute is put in a property, and any parameters are put in “ExeCommand” which is very poorly named.  The parameters in this case are a trick to start up the default browser to the desired URL.  The minor drawback to this is that you see the command window briefly.  I think there is a better way to do this that I’ve seen but not yet tried that involves doing a registry search to locate the default browser executable and then calling it directly.

The second custom action simply opens an Error dialog and exits the installation.

The third piece to this puzzle is to insert the Custom Action into the InstallExecuteSequence:

<InstallExecuteSequence>
    <!-- Takes user to MSXML6 download page to be installed -->
    <Custom Action="OpenMSXML6Download" After="AppSearch">NOT MSXML6 AND Not Installed</Custom>
    <Custom Action="OpenMSXML6DownloadError" After="OpenMSXML6Download">NOT MSXML6 AND Not Installed</Custom>
</InstallExecuteSequence>

One critical piece of information here is to note the “After=AppSearch”.  When I was first trying to get this to work, I dealt with an huge amount of frustration because I had set this to “After=FindRelatedProducts”.  I had chosen this because although I don’t show it above, this particular WiX install also enables the app to install over itself and prevents downgrading, and in that scenario, that’s the After value that you use.  My assumption that it would work to simulate the InstallURL was a very bad one.  The problem is that FindRelatedProducts occurs before AppSearch, which is where the RegistrySearch property is evaluated.  As a result, the MSXML6 property was NEVER defined and I was redirecting to the download page even if MSXML6 was installed.  (BTW this is a good reason to download and install the Windows SDK tools – I only discovered this because I bothered to open the MSI in Orca!)

With these three pieces in place, the installer now correctly detects MSXML6 and will redirect the user to the download page (and terminate) if it isn’t installed.  Note, however, that as presented above, the user may have to go thru the majority of the UI install before this happens.  If you don’t want the user to see any of the UI before the check and redirect takes place, duplicate the InstallExecuteSequence lines into the InstallUISequence section of the WXS file.  I’ve been told it’s somewhat of a bad practice, but that does fall more in line with the way the Visual Studio InstallURL works.

Posted in WiX | Tagged: | 1 Comment »

My wife always said I can’t see 4 feet in front of me…

Posted by Michael Bray on October 20, 2009

At home, I am NOTORIOUS for looking right at things and not seeing them.  This type of conversation has been heard many times thru the years: “Honey, could you pass the sugar?”  “Hmm?  Where is it?”  “Right in front of you, doofus.”

Well this isn’t just at home – it’s here at work too.  And I just found something that (I presume) has been there all the time and I just never saw it.  And I love it.

In SQL Server Management Studio, when you execute a query and display the execution plan, that plan is often (for me anyway) way larger than the screen.  Sure you can right-click and there are various zoom options like ‘Zoom to fit’.  But if you do that, then you are left needing jewelers glasses to see it, and worse, with having to navigate around the plan, which can be a pain.  Or *was* a pain. 

If you look closely (you might have to squint) you’ll see a + below the scroll bar:

image

If you click that +, you get a Panning window that lets you move anywhere in the query plan you want, pain free.  And no need to zoom!

Posted in Uncategorized | Leave a Comment »

Enabling Automatic NTLM Authentication in Firefox

Posted by Michael Bray on October 2, 2009

When browsing using Internet Explorer to a site on an internal network that is configured with Integrated Windows Authentication, such as http://intranet, IE will automatically attempt to use your domain credentials to log in.  It’s always pained me that Firefox didn’t do the same thing.  I thought it had something to do with some M$ hocus pocus undocumented feature magic that prevented FF from being able to do this.  SO wrong.  It’s easy to enable Firefox to do the same:

To enable this feature in Firefox, go to the “about:config” url in Firefox, and then find the configuration parameters for “network.automatic-ntlm-auth.trusted-uris”, “network.negotiate-auth.delegation-uris”, and “network.negotiate-auth.trusted-uris”.  In those values, put the URIs that you want Firefox to automatically pass authentication to.  You can either use the http:// prefix or you can leave it off, and you can specify multiple parameters by separating them with a comma.

Posted in Uncategorized | Leave a Comment »