So you want to provide a DataSet to your WP7 app?!

Hi folks,

It is not new (wow, more than 2 years) that MS announced that there’s no plan to support dataset on Silverlight (currently WP7 is based on Silverlight 3 based).
http://blogs.msdn.com/b/adonet/archive/2009/05/26/dataset-and-silverlight.aspx

But we all know that life is not so easier and we cannot just change to LINQ to Entities. I’m not even going to jump in this discussion here. Actually I am assuming that you need an alternative. One possible alternative is the SilverlightDataset project, created by Laskarzhevsky Software Inc. I have to be honest that I did not used this solution mainly because at the first moment I’m trying to do something really simple.

My general idea is to create a WCF Service that will expose dynamically new types (POCO) which will be created based on a dataset. I played a little bit with Reflection & Emit to do this job. Here is a code that, given a DataTable returns a dynamically created type:

public class Functions
{
    public static Type CreateType(DataTable table)
    {
        //Obtaining DataContract & DataMember Attribute
        CustomAttributeBuilder dataContractAttribute = new CustomAttributeBuilder(typeof(DataContractAttribute)
                                                      .GetConstructor(System.Type.EmptyTypes), new object[] { });
        CustomAttributeBuilder dataMemberAttribute = new CustomAttributeBuilder(typeof(DataMemberAttribute)
                                                      .GetConstructor(System.Type.EmptyTypes), new object[] { });

        AssemblyName assemblyName = new AssemblyName();
        assemblyName.Name = string.Format("DynamicType.{0}", table.TableName);

        AppDomain appDomain = AppDomain.CurrentDomain;
        //Later you just change it to AssemblyBuilderAccess.Run and remove the file saving process to save some time...
        AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name,"dynamic.dll");

        TypeBuilder typeBuilder = moduleBuilder.DefineType(string.Format("Data.{0}", table.TableName), TypeAttributes.Public, typeof(BaseType));
        //decorating as DataContract
        typeBuilder.SetCustomAttribute(dataContractAttribute);

        //calling base contructor passing thw datarow to it
        ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(DataRow) });
        ILGenerator constructorIL = constructorBuilder.GetILGenerator();
        constructorIL.Emit(OpCodes.Ldarg_0);
        constructorIL.Emit(OpCodes.Ldarg_1);
        constructorIL.Emit(OpCodes.Call, typeof(BaseType).GetConstructor(new Type[] { typeof(DataRow) }));
        constructorIL.Emit(OpCodes.Ret);

        //Creating one property per column
        foreach (DataColumn column in table.Columns)
        {
            createDataMemberProperty(dataMemberAttribute, typeBuilder, column);
        }

        Type toReturn = typeBuilder.CreateType();
        // Save the assembly so it can be examined with Ildasm.exe,
        // or referenced by a test program.
        assemblyBuilder.Save("dynamic.dll");

        return toReturn;
    }

    private static void createDataMemberProperty(CustomAttributeBuilder dataMemberAttribute, TypeBuilder typeBuilder, DataColumn column)
    {
        string propertyName = column.ColumnName;
        string fieldName = string.Format("_{0}", propertyName.ToLower());

        //Creating Field that will be set/get on the properties                
        FieldBuilder propertyField = typeBuilder.DefineField(fieldName,
                                            column.DataType,
                                            FieldAttributes.Private);
        // The last argument of DefineProperty is null, because the
        // property has no parameters. (If you don't specify null, you must
        // specify an array of Type objects. For a parameterless property,
        // use an array with no elements: new Type[] {})
        PropertyBuilder property = typeBuilder.DefineProperty(column.ColumnName,
                                                    System.Reflection.PropertyAttributes.None,
                                                    CallingConventions.Standard, column.DataType, null);

        //decorating with DataMember attribute
        property.SetCustomAttribute(dataMemberAttribute);

        // The property set and property get methods require a special
        // set of attributes.
        MethodAttributes getSetAttr =
            MethodAttributes.Public | MethodAttributes.SpecialName |
                MethodAttributes.HideBySig;

        // Define the "get" accessor method for CustomerName.
        MethodBuilder propGetMethod =
            typeBuilder.DefineMethod("get_" + propertyName,
                                        getSetAttr,
                                        column.DataType,
                                        Type.EmptyTypes);

        ILGenerator getIL = propGetMethod.GetILGenerator();
        getIL.Emit(OpCodes.Ldarg_0);
        getIL.Emit(OpCodes.Ldfld, propertyField);
        getIL.Emit(OpCodes.Ret);

        // Define the "set" accessor method for CustomerName.
        MethodBuilder propSetMethod =
            typeBuilder.DefineMethod("set_" + propertyName,
                                        getSetAttr,
                                        null,
                                        new Type[] { column.DataType });
        ILGenerator setIl = propSetMethod.GetILGenerator();

        setIl.Emit(OpCodes.Ldarg_0);
        setIl.Emit(OpCodes.Ldarg_1);
        setIl.Emit(OpCodes.Stfld, propertyField);
        setIl.Emit(OpCodes.Ret);

        // Last, we must map the two methods created above to our PropertyBuilder to 
        // their corresponding behaviors, "get" and "set" respectively. 
        property.SetGetMethod(propGetMethod);
        property.SetSetMethod(propSetMethod);
    }
}

The code look a bit scary… And it really is… Use emit is like develop in IL directly… J Anyway, if you give this datatable:

It will return something like this:

[DataContract]
public class Students : BaseType
{
	private int _age;
	private string _name;

    [DataMember]
	public int Age
	{
		get
		{
			return this._age;
		}
		set
		{
			this._age = ;
		}
	}

	[DataMember]
	public string Name
	{
		get
		{
			return this._name;
		}
		set
		{
			this._name = ;
		}
	}
	public Students(DataRow ) : base()
	{
	}
}

You can see that it is deriving from a base type, called BaseType…

[DataContract]
public class BaseType
{
    public BaseType(DataRow row)
    {
        foreach (DataColumn column in row.Table.Columns)
        {
            var field = this.GetType().GetField(string.Format("_{0}",column.ColumnName.ToLower())
                                                ,BindingFlags.NonPublic|BindingFlags.Instance);
            if (field != null && !row.IsNull(column))
                field.SetValue(this, row[column]);
        }
    }
}

As you can see the base type has a constructor that given a dataRow, it will set the fields of the type. I could implement this logic on the Contructor but I was really lazy to find out how to do it using Emit… Could take some hours to find out… Then I just made the easy way… Another thing that you should not is the WCF attributes [DataContract] and [DataMember] that are decorating the types and properties…

Now we want to expose this datatable through a WCF service… The service contract is quite simple:

[ServiceContract]
public interface IDataSetService
{
    [OperationContract]
    [ServiceKnownType("GetKnownTypes", typeof(DataSetService))]
    DataSet.BaseType GetData();
}    

You can see that it returns the BaseType, which is the basetype of my dataset elements. Another important thing to remark is the ServiceKnownType attribute that I’m using to dynamically add KnowTypes(like this Students class that I showed above). Here you can have the service implementation:

[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class DataSetService : IDataSetService
{

    #region [ServiceKnownType("GetKnownTypes", typeof(DataSetService))]
    /// <summary>
    /// Detects all KnownTypes that should be published by the Service WSDL
    /// Basically we will find all DataContracts, ServiceCommandRequest and ServiceCommandResponses
    /// </summary>
    /// <param name="provider"></param>
    /// <returns></returns>
    public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
    {
        Type dynamicType = DataSet.Functions.CreateType(new Students.StudentsDataTable());
        dynamicTypes = new Dictionary<Type, Type>();
        dynamicTypes.Add(typeof(Students.StudentsDataTable), dynamicType);

        System.Collections.Generic.List<System.Type> knownTypes =
            new System.Collections.Generic.List<System.Type>();
        knownTypes.Add(dynamicType);
        // Add any types to include here.        
        return knownTypes;
    }
    #endregion

    static Dictionary<Type, Type> dynamicTypes;

    public DataSet.BaseType GetData()
    {
        Students.StudentsDataTable table = new Students.StudentsDataTable();
        table.Rows.Add("Pedro", 28);
        DataSet.BaseType toReturn = (DataSet.BaseType)System.Activator.CreateInstance(dynamicTypes[table.GetType()],
                                                                                      new object[] { table.Rows[0] });
        return toReturn;
    }
}

Just to remember, AspNetCompatibilityRequirements is required by the Silverlight 3 Service client. The GetKnownTypes method is doing the miracle to allow me to return dynamic types to my client. Actually when my client generates the proxy classes, the Students class will be available as well! Finally, you can see that the service is returning a created type based on a row with the values “Pedro” and 28.

Let’s add the reference to our Web Service from our WP7 Client…

One you add the Service reference, you can take a look on the generated proxy code

The most important thing to see is that our Students type was created, even if our main WebService API do not refer to it. Actually that GetKnownTypes made this “miracle”!

Now it is time to consume the service from our WP7 app… Look this very simplistic code:

// Constructor
public MainPage()
{
    InitializeComponent();

    // Set the data context of the listbox control to the sample data
    DataContext = App.ViewModel;
    this.Loaded += new RoutedEventHandler(MainPage_Loaded);

    DataSetService.DataSetServiceClient cli = new DataSetService.DataSetServiceClient();
    cli.GetDataCompleted += new EventHandler<DataSetService.GetDataCompletedEventArgs>(cli_GetDataCompleted);
    cli.GetDataAsync();
}

void cli_GetDataCompleted(object sender, DataSetService.GetDataCompletedEventArgs e)
{
    DataSetService.Students students = (DataSetService.Students)e.Result;
    MessageBox.Show(string.Format("{0}, {1}", students.Name, students.Age));
}

Basically this code just call the method GetData from our service… Look that when the GetDataCompleted event we just cast the result to (DataSetService.Students) . Then we just show a messageBox… Really simple… And it worked nicely!

Well… This is it! That was an easy way to expose your dataset to WP7 clients using WCF without bigger issues. Of COURSE this sample is really simplistic but I’m sure that it can handle a lot of situations. In case of some special data editing support we would need some extra code to synchronize the POCO class with the dataset… But you know… this is just the start…. J

DOWNLOAD: Naturally you can have the sourceCode of this sample here.

About these ads

Posted on 20.07.2011, in Step-By-Step. Bookmark the permalink. 2 Comments.

  1. David Frija

    Really nice!!! Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: