29 Oct 2009

Dynamic Linq OrderBy - Sort Field known only at Run-Time

A common issue with my LinqToSql stuff is the need to do an OrderBy on an IQueryable a la:
myQueryable = myQueryable.OrderBy(item => item.mySortField)

However, often you won't know the sort field until runtime, e.g. for a dynamically sortable grid.

One solution is to use the DynamicQueryable library provided by MS. It lets you do string-based dynamic querying like so:
myQueryable = myQueryable.OrderBy(mySortFieldName + " ASC"); // or use DESC if you like it in descending order

However this didn't have an option for using an IComparer to sort the results, and I wanted to use my NaturalSortComparer for natural case sorting. The solution I came up with was this:
myQueryable = myQueryable.OrderBy(item => item.GetReflectedPropertyValue(mySortFieldName ));

Or, actually employing NaturalSortComparer, like this:
myQueryable = myQueryable.OrderBy(item => item.GetReflectedPropertyValue(mySortFieldName ), new NaturalSortComparer<string>());

The solution uses a little helper method called GetReflectedPropertyValue():

public static string GetReflectedPropertyValue(this object subject, string field)
{
object reflectedValue = subject.GetType().GetProperty(field).GetValue(subject, null);
return reflectedValue != null ? reflectedValue.ToString() : "";
}

OK, Reflection is slow (not sure how it compares to building a Lambda Expression tho) but it does the job for my purposes.

28 Oct 2009

AjaxControlToolkit Accessible Tabs

Can't believe it but MS forgot to make Tabs in the Ajax Control Toolkit accessible via the keyboard. Okay, they provide an access key property, but what about just being able to tab to the tab (ho ho) and press enter to open?

My solution is a snippet of JQuery to iterate through all the tabs and update the HeaderText of the TabPanel, turning it into a fake Html Anchor. Works fine in FF3 and IE6+ so I'm happy.

$('.ajax__tab_tab').each(function() { if ($(this).html().indexOf('<a') < 0)
$(this).html('<a href="" onclick="return false;">' + $(this).html() + '</a>'); });

If you want to hide the 'linkiness' of the updated headertext, just add a style directive to your CSS like so:

.ajax__tab_tab a { text-decoration: none; font-weight: normal; }

Probs? Let me know.

15 Oct 2009

JQuery and setting checkbox to checked except disabled ones....

Got a master checkbox that you want to control the toggling of a bunch of other checkboxes in a container (but only non-disabled ones)? Want to use JQuery to make it SUPER EASY?

$('#chkMasterCheckbox').click(function(event) {
$('#divContainer input[type=checkbox]:enabled').attr('checked', $(this).attr('checked'));
});


Oh yeah baby.

4 Sept 2009

Natural Sort Compare with Linq OrderBy

Having "fun" with IComparer<T> when trying to do OrderBy in Linq with a custom sort?

Me too. I wanted to sort some rows from a DB table by a string 'filename' field. The standard Linq (and SQL) OrderBy(item => item.sortProperty) sorts the strings like so:

image1.jpg
image200.jpg
image30.jpg
image4.jpg

But because I am a human being, I don't want that. No, instead I wanted them sorted like:

image1.jpg
image4.jpg
image30.jpg
image200.jpg

What I want is "Natural Sort". C# doesn't offer a built-in solution, but the Linq OrderBy method does have an override that allows you to use your own IComparer<T> function, a la
myLinqQuery.OrderBy(item => item.sortProperty, new MyComparer<string>())


(Edit: Incidentally, I now have a way of doing this dynamically, if you don't know what sort field will be used until run-time. See here.)

Building your own "Natural Sort" IComparer is not for the faint of heart, or the lazy, so I just nicked some code from Justin Jones and tweaked it a bit:

    public class NaturalSortComparer<T> : IComparer<string>, IDisposable
    {
        private bool isAscending;

        public NaturalSortComparer(bool inAscendingOrder = true)
        {
            this.isAscending = inAscendingOrder;
        }

        #region IComparer<string> Members

        public int Compare(string x, string y)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComparer<string> Members

        int IComparer<string>.Compare(string x, string y)
        {
            if (x == y)
                return 0;

            string[] x1, y1;

            if (!table.TryGetValue(x, out x1))
            {
                x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
                table.Add(x, x1);
            }

            if (!table.TryGetValue(y, out y1))
            {
                y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
                table.Add(y, y1);
            }

            int returnVal;

            for (int i = 0; i < x1.Length && i < y1.Length; i++)
            {
                if (x1[i] != y1[i])
                {
                    returnVal = PartCompare(x1[i], y1[i]);
                    return isAscending ? returnVal : -returnVal;
                }
            }

            if (y1.Length > x1.Length)
            {
                returnVal = 1;
            }
            else if (x1.Length > y1.Length)
            { 
                returnVal = -1; 
            }
            else
            {
                returnVal = 0;
            }

            return isAscending ? returnVal : -returnVal;
        }

        private static int PartCompare(string left, string right)
        {
            int x, y;
            if (!int.TryParse(left, out x))
                return left.CompareTo(right);

            if (!int.TryParse(right, out y))
                return left.CompareTo(right);

            return x.CompareTo(y);
        }

        #endregion

        private Dictionary<string, string[]> table = new Dictionary<string, string[]>();

        public void Dispose()
        {
            table.Clear();
            table = null;
        }
    }


The first time I tried to use this, it failed with the rather useless error: "Unsupported overload used for query operator 'OrderBy'."

Turns out it was because I had tried to use my custom OrderBy on the Linq query before it had got the data records from the server, and hence it thought I was trying to run the natural sort in SQL. So I fixed the prob by getting the results first with a quick call to AsEnumerable(), a la:

List<Photo> photos = DataManager.MainContext.Photos
     .Where(item => item.PhotoFilename != null)
     .AsEnumerable()
     .OrderBy(item => item.PhotoFilename, new NaturalSortComparer<string>())
     .ToList();


Works a treat!

26 Aug 2009

ASP.NET Web Form Code Blocks

Just found this neato summary of all the different ASP.NET web form code-block / data-binding syntax options, i.e. <%= , <%# etc.

http://quickstarts.asp.net/QuickstartV20/aspnet/doc/pages/syntax.aspx

It was nice to put a name to the different syntax types :) Also, I didn't know about the ability to register C# variables inline i.e <object id="DateOfBirth" class="System.DateTime" runat="server"> , although I'm not sure when I would use it :)

Also, this blog posting helped clear up my "voodoo" perception of the Container and Eval syntax available when databinding in a template:

http://weblogs.asp.net/rajbk/archive/2004/07/20/what-s-the-deal-with-databinder-eval-and-container-dataitem.aspx

7 Aug 2009

Running VS2003 on Vista Business with debugging

It's a pain in the arse!

1. Install VS2003
2. Install .NET 1.1 SP1 (download from MS)
3. In Control Panel > Classic View > Programs and Features > Turn Windows Features On or Off :

IIS > Web management Tools > IIS 6 Management Compatibility
IIS > Web management Tools > Management Console
IIS > World Wide Web Services > Security > Basic Authentication
IIS > World Wide Web Services > Security > Windows Authentication

4. In IIS7 (Control Panel > Classic View > Administrative Tools > IIS Manager)

- Click on the Server node and make sure you're in Features View
- Select ISAPI and CGI Restrictions
- Make sure ASP.NET v1.1 is "Allowed"

5. In IIS7 (Control Panel > Classic View > Administrative Tools > IIS Manager)

- Click on the web site node (or your V1.1 application if you've installed it under the default node)
- Click on Authentication and enable Windows Authentication

6. In Local Users and Groups (Control Panel > Classic View > Administrative Tools > Computer Management)

- For Group "Debugger Users", add your user account and the IUSR account
- For Group "VS Developers", add your user account and the IUSR account

30 Jul 2009

Ironic Ads

Moh! Funneh.

“Label For”, Selects and IE6

Good accessible sites use LABEL tags to describe the purpose of other controls on the page; for example a login control should have LABELs in it that tells the user which textbox is for the Username and which is for Password. To be completely groovy the LABEL should also have a FOR attribute in it that explicitly declares which control it refers to, like so:



<label for=”txtUsername”>Username</label>

<input type=”text” id=”txtUsername” />


BUT there is one problem – f*%!&ing IE6 does something very stupid when you use LABEL FOR with dropdownlist boxes (SELECTS). In IE6, if you click on the label, the SELECT resets its currently selected item and reverts to the first in the list. GOD *DAMN* IT.


Anyway, I knocked up this blob of JQuery which empties the LABEL FOR with SELECTs in IE6 and below. Hope it’s useful to someone.


<!– Script to disable Label.For attributes for SELECT elements in IE6 (otherwise they reset the selected OPTION)  –>


<!–[if lte IE 6]>

<script type=”text/javascript”>

$(’label’).each(function() {if ($(’#’ + $(this).attr(’for’)).attr(’tagName’) == ‘SELECT’) $(this).attr(’for’, ”) ;});

</script>

<![endif]–>

7 Jul 2009

Interviewing IT Development Candidates

Man, I've pondered on the best way to do this so many times, and according to Joel, I've made many of the classic mistakes in the past:

http://www.joelonsoftware.com/articles/GuerrillaInterviewing3.html

What a great site.

30 Jun 2009

Faking and Mocking: HttpContext and HttpSessionState

HttpContext.Current is all well and good until you want to do unit tests on your business logic, and they explode because there's a reference to the Session collection somewhere, which returns null because you're not in a web context. You get to thinking that your business logic should be context-agnostic anyway, and so you want to provide your own context objects for Session, Items, User etc.

So then you run the gamut of HttpContext fakery, thinking about moq, TypeMock etc - only you don't want to go to all that trouble. Phil Haack's HttpSimulator hoves into view but just doesn't feel right for this particular task (although it IS uber cool). His SimulatedHttpRequest class was pretty cool but sadly the Session object was null when I referenced it.

Finally I stumbled on this post (specifically the one by radmanmm). Using the GetMockHttpSessionState() with my SimulatedHttpRequest object solved the problem. Wooot!

PS. Mr Walther's Fake Intrinsic Objects looked pretty cool too, check them out.
If I helped you out today, you can buy me a beer below. Cheers!