Blog Home  Home Feed your aggregator (RSS 2.0)  
ObjectDataSource and Generics (part 2) - Manuel Abadia's ASP.NET stuff
 
# Wednesday, March 28, 2007

In a previous post I said that the ObjectDataSource can handle generic types using the backtick (`) notation, even if the ObjectDataSource doesn't have any design time support for this.

This works because the ObjectDataSource internally calls to Type.GetType to resolve type names to Type objects. As the Type.GetType method handle generics transparently, the ObjectDataSource does not have to do anything special to support it at runtime. Supporting generic types at design time is more complex so they fiter out generic types in the "Select Type" wizard step.

For generic methods things change. There is some inconsistency in generics handling in the .NET Framework. If you get the type of a class like List<Customer>, its Name is List`1[Customer]. However, if you have a method like Customer Get<Customer>(int id), the method name is Get. IMHO, to have an orthogonal implementation, the method name should be Get`1[Customer]. Also, the GetMethod method should understand the backtick notation for methods and retrieve the specified method properly but it isn't supported for now. I have requested an improvement for the next version of the .NET framework here. Probably they won't fix it but it would be nice if you vote for it.

Returning to the ObjectDataSource... When the ObjectDataSource tries to find a method, it call the GetMethods method and compares the method name with the expected name, filtering out generic methods.

So, I think Microsoft didn't bother to add support for generic types or generic methods, and if generic types work at runtime is by luck (because of the GetType behaviour).

As more developers are using generics, there is a need to properly support generic methods and generic types. The next version of the ExtendedObjectDataSource (that will be released next week), will have full generics support, at design time and at runtime.

It wasn't easy to add design time support for generics as several problems appeared:

* Generic type expressions and generic method expressions can be arbitrarily complex (like MyGraph`2[[Utilities.Set, Utilities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a7a65c534534e209],[Utilities.Graph, Utilities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a7a65c534534e209]], MyGraphLibrary, Version=1.0.0.0, Culture=neutral, PublicKeyToken=2134afb4534e209), so I had to write a parser for both kinds of expressions.
 
* At runtime, specifying "__code" for the assembly name gets traslated to the dynamically generated assembly (as explained in the previous post). However, at design time this translation has to be done manually.
 
* I had to rewrite the select type wizard step and select method wizard step as the code was getting harder to maintain with generics.

* Showing and choosing generic methods and types is half of the problem, after choosing them you have to create the generic type or the generic method based on the generic type definition and the generic method definition.
 
Lets see an example of what can be accomplished now with the CompatObjectDataSource.

Imagine that we want to be able to register some objects at the start of the application in order to used later using a registry.

The registry can be implemented like this:

/// <summary>Global Registry.</summary>

public class Registry

{

    #region Fields

 

    private static Registry _instance = new Registry();

 

    private Hashtable _registryStore = new Hashtable();

 

    #endregion

 

    #region Properties

 

    /// <summary>Gets the unique instance for this class.</value>

    public static Registry Instance

    {

        get { return _instance; }

    }

 

    #endregion

 

    #region Methods

 

    /// <summary>Private constructor to avoid creating instances of this class.</summary>

    private Registry() { }

 

    /// <summary>Registers an object.</summary>

    /// <param name="key">The key.</param>

    /// <param name="obj">The object to register.</param>

    public void RegisterObject<K, T>(K key, T obj)

    {

        if (_registryStore.Contains(key)) {

            throw new Exception(String.Format("Error, there is an object stored using the '{0}' key" + key.ToString()));

        }

 

        _registryStore[key] = obj;

    }

 

    /// <summary>Registers an object list.</summary>

    /// <param name="key">The key.</param>

    /// <param name="objects">The object list to register.</param>

    public void RegisterObjectList<K, T>(K key, IList<T> objects)

    {

        if (_registryStore.Contains(key)) {

            throw new Exception(String.Format("Error, there are objects stored using the '{0}' key" + key.ToString()));

        }

 

        _registryStore[key] = objects;

    }

 

    /// <summary>Gets an object from the registry.</summary>

    /// <param name="key">The key.</param>

    /// <returns>The object stored on the registry or null if it wasn't found.</returns>

    public T GetObject<K, T>(K key)

    {

        return (T)_registryStore[key];

    }

 

    /// <summary>Gets a list of objects from the registry.</summary>

    /// <param name="key">The key.</param>

    /// <returns>The list of objects stored on the registry or null if it wasn't found.</returns>

    public IList<T> GetObjectList<K, T>(K key)

    {

        return _registryStore[key] as IList<T>;

    }

 

    #endregion

}


   
Some of the data stored in the registry when the application starts is a list of all available countries (for example, retrieved from the database):

// register the countries

Registry.Instance.RegisterObjectList<string, Country>("countries", countries);

The Country class is very simple:

namespace Manu.GenericTests

{

    public class Country

    {

        private int _id;

        private string _name;

        private string _code;

 

        public int Id

        {

            get { return _id; }

            set { _id = value; }

        }

 

        public string Name

        {

            get { return _name; }

            set { _name = value; }

        }

 

        public string Code

        {

            get { return _code; }

            set { _code = value; }

        }

 

        public Country()

        {

        }

 

        public Country(int id, string name, string code)

        {

            _id = id;

            _name = name;

            _code = code;

        }

    }

}

If we want to show all the countries in any webform we can use an CompatObjectDataSource. After dropping one to the toolbox we configure the type:

And then we can see the list of methods compatible with the Select operation (GetObject and GetObjectList):

As both methods are generic method definitions, after selecting one of them, we have to specify the generic parameters. In our case, we stored the countries using a string key, and we stored a List of Country objects so the generic parameters for the method are System.String, Manu.GenericTests.Country. After clicking in "Make Generic Method" we have the data source configured properly:

If we take a look to the ASPX we can see the following code:

<manu:CompatObjectDataSource ID="CompatObjectDataSource1" runat="server"

    SelectMethod="GetObjectList`2[System.String, [Manu.GenericTests.Country, __code]]"

    TypeName="Manu.GenericTests.Registry, __code"

    OnObjectCreating="ObjectDataSource1_ObjectCreating">

        <SelectParameters>

            <asp:Parameter Name="key" Type="String" />

        </SelectParameters>

</manu:CompatObjectDataSource>

 

The SelectMethod is saved as "GetObjectList`2[System.String, [Manu.GenericTests.Country, __code]]" and the TypeName is saved as "Manu.GenericTests.Registry, __code".

The only thing to do now is to set the DefaultValue property for the key parameter to countries. Now the CompatObjectDataSource has enough information to call to GetObjectList<string, Country>("countries") from the Registry class and retrieve the countries.

The only thing I'm missing in the CompatObjectDataSource/ExtendedObjectDataSource is support for Bind expressions for nested properties (<%# Bind(Address.PostalCode) %>), but unfortunately, the framework can not understand those expressions so nothing can be done ast the moment. I wrote an email to ScottGu about that and got this reply from Shanku Niyogi:

"This is definitely a doable thing - we'll add it to our list of items we're considering adding for an upcoming release."

So keep your fingers crossed.

I'm open to suggestions in what to functionality you would like to see in the ObjectDataSource/CompatObjectDataSource/ExtendedObjectDataSource.

Wednesday, March 28, 2007 12:35:22 AM (Romance Daylight Time, UTC+02:00)  #    Comments [1]   ASP.NET | Microsoft .NET Framework  | 
Copyright © 2014 Manuel Abadia. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.