blog community

Welcome to blog community Sign in | Join | Help
in Search

Patrick Gilhuijs

  • How to enable sorting on a GridView using an IEnumerable list as datasource

    For the last months I’ve been working together with my colleague Raimond Brookman on new software for our software factory named Endeavour. The software is asp.net related. Today I ran into a problem with Microsofts GridView control. I was  using  an ObjectDataSource control that can provide a list of domain objects of type IEnumerable. This was all working fine until I wanted to enable sorting. I found out that built in  sorting capability of the GridView  control is not supported when using an object of type IEnumerable as a datasource. So I converted the IEnumerable  list to a DataTable. But this class doesn’t support Nullable types and automatically converts my enum values to integers..:( That’s not what I wanted/needed.

    Then I started thinking whether it could be possible to have this basic sorting done by using Linq! Well, it certainly is possible. Enumerable.OrderBy<> is your friend when it comes to sorting of a List of domain objects that need to be shown in a GridView. You can use Linq like this for example:

    List<Order> orders = new List<Order>();

    //<some code to fill the list... >

    orders.OrderBy<Order, string>(o => o.OrderDate);

    This code example above will sort the orders list ascending using the OrderDate property. It’s also possible to do a multi sort (sort on two properties) using ThenBy<> after the OrderBy<> method, like this:

    List<Order> orders = new List<Order>();

    //<some code to fill the list... >

    orders.OrderBy<Order, string>(o => o.OrderDescription).ThenBy<Order, DateTime?>(o => o.OrderDate);    

    There are also OrderByDescending or ThenByDescending equivalents of the methods…

     Now, in my piece of software that I’m building I don’t know upfront with what type of domain object I’m dealing with. So I cannot use the above statements (or something like that) in my code. Runtime I’ll have a list with domain objects, knowlegde of the type of the domain object and I’ll get the sort expression passed from the GridView control. Wouldn’t it be nice to be able to dynamically create a linq query with a lambda expression as in the above example…. It’s nice and also possible J! Here’s some code that will do exactly that. The method in the example below  gets  a list of objects, a sort expression (multi sort expression)  and the type of the domain object passed as parameters. It builds the correct expression tree and executes it on the list. And voila, there’s a list of type IOrderedEnumerable that the GridView control can use to bind against. This is the first version of the code, I didn't test it thoroughly yet so there migth be some bugs in there. May be it also needs some refactoring, but it is just provided as an example to show how one could do this.

    private IEnumerable  SortSelectResult(IEnumerable selectResult, string sortExpression, Type searchResultType)

     {

         string[] expressions = sortExpression.Split(',');

         bool isFirstLamdaExpression = true;

         MethodCallExpression finalCallExpression = null;

         ///walk through the parts of the sortExpression (e.g. "Description ASC, OrderDate DESC")

         foreach (string expression in BLOCKED EXPRESSION

         {

             //Cut the SortDirection part loose from the column name

             string[] expressionParts = expression.Split(' ');

             //Determine sort direction

             SortDirection sortDirection = DetermineSortDirection(expressionParts);

             //Get the propertyInfo for this property

             PropertyInfo propInfo = searchResultType.GetProperty(expressionParts[_ColummNamePartIndex]);

             Type propertyType = propInfo.PropertyType;

            

             //Build a lambda expression like this:

             //pagedOrders.OrderBy<Order, string>(c => c.OrderDescription).ThenBy<Order, DateTime>(c => c.OrderDate);

             //to enable sorting a enumerable list.

             ParameterExpression param = Expression.Parameter(searchResultType, propInfo.Name);

             Expression selector = Expression.Property(param, propInfo);

             LambdaExpression lambdaExpression = Expression.Lambda(selector, param);

             if (isFirstLamdaExpression)

             {

                 string methodName = "OrderBy";

                 if (sortDirection == SortDirection.Descending)

                 {

                     methodName += "Descending";

                 }

                 MethodCallExpression orderByCall =

                           Expression.Call(typeof(Enumerable), methodName, new Type[] { searchResultType, propertyType },

                                  Expression.Constant(selectResult), lambdaExpression);

                 finalCallExpression = orderByCall;

                 

      }

            else

            {

                 string methodName = "ThenBy";

                 if (sortDirection == SortDirection.Descending)

                 {

                     methodName += "Descending";

                 }

                 MethodCallExpression thenByCall =

                              MethodCallExpression.Call(finalCallExpression, methodName,

                                               new Type[] { searchResultType, propertyType },

    Expression.Constant(selectResult), lambdaExpression);

                 finalCallExpression = thenByCall;

            }

        }

       

        if (finalCallExpression != null)

        {

            selectResult = (IEnumerable)selectResult.AsQueryable().Provider.Execute(finalCallExpression);

        }

         return (IEnumerable)selectResult;

    }

    private SortDirection DetermineSortDirection(string[] expressionParts)

    {

         if (expressionParts.Length <= 1 ||

             string.IsNullOrEmpty(expressionParts[_SortDirectionPartIndex]) ||

             expressionParts[_SortDirectionPartIndex].ToUpperInvariant() == "ASC")

         {

             return SortDirection.Ascending;

         }

         else if (expressionParts[_SortDirectionPartIndex].ToUpperInvariant() == "DESC")

         {

             return SortDirection.Descending;

         }

         else

         {

             return SortDirection.Ascending;

         }

    }

      The OrderBy(Descending) / ThenBy(Descending)  methods have also an overload where you can provide a comparer class, so that you can do some advanced stuff when needed. May be I'll need it also but I don't now yet ; ) Also I must say that the GridView control doesn't support multi sort out of the box, but it's not that hard to implement that.

    Hope this helps!

     

     

  • Change standard content of Class1.cs when adding a new Class with Visual Studio

    Today it frustrated me again that every time I add a new class in visual studio 2005 (or Orcas) I have to add the 'public' access modifier to the class I created. I decided to do something about it.

    The class template that VS uses to create a new class can be found in this zip file (in case of CSharp):

    C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\ItemTemplates\CSharp\1033\Class.zip or
    C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\ItemTemplates\CSharp\Code\1033 when using VS Orcas

    Open the zip file, add the public keyword, save it and you're done. Every time you add a class it will be public.

    Of course you can make other changes you like and there are also other zip files with templates in it. I think the names speak for themselves.

  • Wireless violence....

    Last week I received all the gear to enable my home  telephone and Internet connection (InternetPlusBellen) via KPN, a Dutch Telecom Provider. Because I have my desktop and wireless printer located on the upper floor and use my laptop throughout the whole house, I wanted to create a wireless network using the KPN Experia box, which is also a wireless access point, and my own wireless router (Asus WL500g). The only thing I knew was that it should be possible to link those two devices together. After some browsing through the settings of both devices I discovered that WDS was the way to go (Wiress Distribution System). But....I couldn't get it to work. Also a Google session searching for problems related to my wl500g and the Experia box didn't produce a solution. After a long, resultless evening, I decided to give up for that day and to have some sleep..:) The day after I googled on WDS specifically. I should have done that earlier :)
    Both on the wl500g and the KPN box WPA-PSK encryption was enabled (by default), but it turns out that WDS can not be used in combination with WPA-PSK. WPA-PSK encrypts all MAC-addressess while WDS needs the MAC-addresses to communicate! So, if you ever should want to link wireless devices together using WDS use WEP encryption and not WPA-PSK.

    Hope this helps!

  • How to delete a team system project

    Until yesterday I wasn't aware of the fact that it is possible to delete a team project. It's not possible using the UI though, but through a command-line utility. The util is called TFSDeleteProject.exe and is located in <drive >\Program Files\Microsoft Visual Studio 8\Common7\IDE.

    Usage: TFSDeleteproject [/q] [/force] [/server:servername] team project name (Use quotation marks if the team project name contains spaces)

    On the internet I read an article that you should not delete a team project when there are still files checked out...but I didn't test that yet.
    You can expect (in a happy scenario) output like this:
    Deleting from Build ...
    Done
    Deleting from Work Item Tracking ...
    Done
    Deleting from Version Control ...
    Done
    Deleting Report Server files ...
    Done
    Deleting SharePoint site ...
    Done
    Deleting from Team Foundation Core ...
    Done

    Microsofts documentation about the tool can be found here.

  • Team System caches RegistrationEntries client side.

    For about two months I have been working on an adapter that reads data from a source database and uploads it to custom dimensions in the TfsWarehouse. Yesterday I ran into a strange (at least, at that moment) problem. First I'll explain the situation.

    In the adapter I have a piece of code that queries the RegistrationEntries from the TfsIntegration database using the IRegistration service. The RegistrationEntry contains the database name of my source database and the database server name. I added the RegistrationEntry using the tfsreg.exe tool, providing an xml file with the data to store. An example of such an xml file is provided below. 

    xml version="1.0" encoding="utf-8" ?>

    <
    RegistrationEntries>
      <
    RegistrationEntry>
        <
    Type>MyEntryType<Type>
        <
    ChangeType>Add<ChangeType>
        <
    Databases>
          <
    Database>
            <
    Name>MyDB<Name>
            <
    DatabaseName>MyDbName<DatabaseName>
            <
    SQLServerName>MyDBServer<SQLServerName>
            <
    ConnectionString>Server=@SQLServerName@;Database=@DatabaseName@;Integrated Security=SSPI<ConnectionString>
          <Database>
        <Databases>
      <RegistrationEntry>
    >

    If you want access this information from code you do can something like this:

    TeamFoundationServer server = TeamFoundationServerFactory.GetServer("http://mytfs:8080");

    RegistrationEntry[] entries = ((IRegistration)server.GetService(typeof(IRegistration))).GetRegistrationEntries("MyEntryType");
    RegistrationEntry entry = entries[0];

    Console.Write(RegistrationUtilities.GetConnectionString(entry, "MyDB"));

    The RegistrationUtilities class replaces the @SQLSevername@ and @DatabaseName@ by the corresponding elements of the RegistrationEntry and creates the ConnectionString for you.
    If you just need to access the database name or the server name you can do that like this:

    string databaseName = entry.Databases[0].DatabaseName;   //using index 0 assuming that there's just one database
    string serverName = entry.Databases[0].SQLServerName;    //to keep the example simple
    string name = entry.Databases[0].Name;

    Well, this should help you getting it to work if you would want to. Let's get on to my problem now.
    I used the tfsreg.exe tool again to update the setting to point to another database. Unfortunately the setting didn't change when I tried to access it from code. Thinking the setting is cached by tfs I thought that an iisreset would do the thing....Not! Then I started thinking and searching for a location and file where tfs could store this information client side. It turned out that different kinds of settings and information is stored (per user) in different files in this folder:
    C:\Documents and Settings\<user name>\Local Settings\Application Data\Microsoft\Team Foundation\1.0\Cache

    On my machine it contains two files: ServerMap.xml, VersionControl.config and folder with a name like _http. The ServerMap.xml contains key/value entries for a service (e.g. http://mytfs:8080/Services/v1.0/Registration.asmx) and the instance id.
    The VersionControl.config contains some info about workspaces etc.

    Most important in my case was the file RegProxyFileCache.xml in the _http folder. It contains all the RegistrationEntries from the TfsIntegration database. So that's the place where it gets the information from. I didn't figure out what the cache-time out is. If you run into this problem also, delete the file and you'll get the updated RegistrationEntry.

    Hope this helps!

Powered by Community Server, by Telligent Systems