Categories
Tips

Sorting an ASP.NET GridView with a custom data source

The other day, I had to present some data in an ASP.NET GridView control and I had to allow sorting of some of the columns. This is easy if the data source is based on e.g. an SQL data source but not particularly easy when using a custom data source. In my case, I had a simple array of custom objects containing invoice data.

There are many solutions for this problem on the web but none of them really seemed to do the trick in my case. Many of the solutions I could find rely on converting the data source into a DataTable object but this is not a good idea when sorting needs to be applied to many fields because the DataTable needs to have these fields added manually.

So without further ado, here is a simple solution for the problem. First, let’s say our custom data source consists of an array of Person objects, defined like this:

class Person {
  string Name { get; set; }
  DateTime BirthDay { get; set; }
}

The GridView to bind the persons to is very simple:

<asp:GridView ID="PersonGridView" runat="server"
  AutoGenerateColumns="false"
  AllowSorting="true"
  OnSorting="personGridView_Sorting">
  <Columns>
    <asp:BoundField DataField="Name" SortExpression="Name" />
    <asp:BoundField DataField="BirthDay" SortExpression="BirthDay" />
  </Columns>
</asp:GridView>

The SortExpression has to be exactly the same name as the field. The sort direction and sort expression are stored in the ViewState and accessed by two fields in code-behind:

private SortDirection CurrentSortDirection {
  get {
    if (ViewState["SortDirection"] == null)
      ViewState["SortDirection"] = SortDirection.Ascending;
    return (SortDirection)ViewState["SortDirection"];
  }
  set { ViewState["SortDirection"] = value; }
}

private string CurrentSortExpression {
  get {
    if (ViewState["SortExpression"] == null)
      ViewState["SortExpression"] = "Name";
    return (string)ViewState["SortExpression"];
  }
  set { ViewState["SortExpression"] = value; }
}

Notice that I have chosen to have Name sorted in ascending order by default. This is of course not necessary but convenient for later. Next up is the OnSorting event:

protected void personGridView_Sorting(object sender, GridViewSortEventArgs e) {
  CurrentSortExpression = e.SortExpression;

  // Ignore e.SortDirection since it is always Ascending, an apparent bug in .NET
  if (CurrentSortDirection == SortDirection.Ascending)
    CurrentSortDirection = SortDirection.Descending;
  else
    CurrentSortDirection = SortDirection.Ascending;

  BindPersonData();
}

Finally, we can write our binding method.

private void BindPersonData() {
  Person[] persons = GetPersonArrayFromSomewhere();

  // Sort the persons, if sorting is applied.
  if (!string.IsNullOrEmpty(CurrentSortExpression)) {
    System.Reflection.PropertyInfo personInfo = (typeof(Person)).GetProperty(CurrentSortExpression);
    if (CurrentSortDirection == SortDirection.Ascending) {
      persons = persons
        .OrderBy(person => personInfo.GetValue(person, null))
        .ToArray();
    }
    else {
      persons = persons
        .OrderByDescending(person => personInfo.GetValue(person, null))
        .ToArray();
    }
  }

  PersonGridView.DataSource = persons;
  PersonGridView.DataBind();
}

The sorting can also be applied with a different sorting method. I happen to like Linq so that is why I have used it here. The key thing to notice here is how the correct field for sorting is found with the use of the reflection framework:

System.Reflection.PropertyInfo personInfo = (typeof(Person)).GetProperty(CurrentSortExpression);

That is why it is essential that the SortExpression value in the GridView is the same as the field’s name.

Oh and by the way, paging with a custom data source is very easy. Add this to your GridView:

AllowPaging="true"
OnPageIndexChanging="personGridView_PageIndexChanging"

and this method to the code-behind:

protected void personGridView_PageIndexChanging(object sender, GridViewPageEventArgs e) {
  PersonGridView.PageIndex = e.NewPageIndex;
  BindPersonData();
}
Categories
Tips

Basic Http authentication from .NET to PHP webservice

Today, I had a major problem that had a not-so-complex-but-rather-stupid solution so I thought it might be worth sharing since I only found online solutions that were rather old.

I have remote access to a PHP webservice, written using NuSOAP. I call the methods of this webservice from a .NET application. This is no problem at all and I can easily call most webservice methods from within .NET. But some of the methods require basic authentication and now the trouble begins.

One would think that it would be enough to just set the credentials for the webservice and make sure that it authenticates on every request. Like this:

MyPhpWebservice ws = new MyPhpWebservice();
ws.PreAuthenticate = true;
CredentialCache cache = new CredentialCache();
cache.Add(
   new Uri(ws.Url),
   "Basic", 
   new NetworkCredential(wsUsername, wsPassword)
);
ws.Credentials = cache;
ws.CallSomeFunctionThatNeedsAuth();

Alas, this did not work. In many places online, people say that .NET does not actually send the authentication details on the first request for some webservice function. If this was true, the solution would simply be:

ws.CallSomeFunctionThatNeedsAuth(); // Does not auth
ws.CallSomeFunctionThatNeedsAuth(); // Should auth now?

This did not work either. Other people in other places online say that one needs to manually add the authentication details to the HttpWebRequest header. But the Visual Studio auto-created MyPhpWebservice class does not expose its WebRequest object so it cannot be directly altered. However, we can override the method GetWebRequest for the webservice and that way manually add the authentication header. This took me a while to figure out but here is a simple solution:

class MyOverriddenPhpWebservice : MyPhpWebservice
{
    protected override System.Net.WebRequest GetWebRequest(Uri uri)
    {
        HttpWebRequest request;
        request = (HttpWebRequest)base.GetWebRequest(uri);

        if (PreAuthenticate)
        {
            NetworkCredential cred =
                Credentials.GetCredential(uri, "Basic");
            if (cred != null)
            {
                byte[] credBuffer = new UTF8Encoding().GetBytes(
                    cred.UserName + ":" + cred.Password);
                request.Headers["Authorization"] =
                    "Basic " + Convert.ToBase64String(credBuffer);
            }
        }
        return request;
    }
}

The code that I wrote in Listing 1 is now perfectly valid and all I need to do is exchange MyPhpWebservice with MyOverriddenPhpWebservice.

And the best part: It works :-)

Categories
Tips

Dual language spell checking in Chrome and Mac OS

Mac OS has a built-in spell checker. So does Google Chrome. This has caused confusion for me recently but the problem is now solved.

For example, at the end of an email, we often write “Med venlig hilsen” in Danish which means something like “With kind regards”. Chrome underlines “venlig” with a red line, indicating a miss-spelled word, even when I have chosen Danish as the language in Chrome. When right-clicking the word, Chrome then suggests “venlig” (i.e. exactly the same word) as an alternative. Puzzling.

It turns out that the underline is actually due to Mac OS’ spell checker and not Google Chrome’s. And the language of the spell checker in Mac OS is set to “Automatic by language”. Obviously, it does not do a good job at detecting Danish but switching the order, in which the languages are detected, solves the problem.

If anyone else ever encounter this problem, here is how to fix it. In Mac OS X, go to System preferences – Language and text – Text and press the dropdown menu for “spelling”. In this menu, press setup and then drag and drop Danish (or what ever other language) to the top, before English.

The interesting thing is that English is still being correctly recognized by Mac OS so I am not getting any red lines for this blog post, although Danish now has precedence over English in the language detection. And I can finally avoid all those red lines when writing my mother tongue.

Med venlig hilsen
David