I am at wits end and I have seen similar posts asking the same question with no clear answers… I understand that in typical situations you know what fields your looking for and expect
from an xml feed or data query via a web service.
However, in my situation, I am working with data in which a datatset contains different columns based on access (not everyone can see the same fields) and user administration… meaning
admin users can add new fields or remove existing ones. With that being the case… I can not hard code any list<> objects and define fieldnames within my ASMX web service or SL project as most examples and forums suggest.
So I have a web service that is grabbing data and returning the results as an XML string. I am now trying to figure out how to take that result and bind it to a DataGrid while not
knowing what the Fields are until I parse the XML string.
My native language is PHP, however I’m learning C# as best as I can… and this issue has really stumped me. I would “Greatly” appreciate any help on this issue…
rather learning how to create dynamic list objectsor some other method in which I can parse an XML feed and then create “Something” that the DataGrid can bind with ?
Hi, the best dynamic list<> is the ObservableCollection<> in namespace System.Collections.ObjectModel. As you know the DataGrid accepts an IEnumerable<> as its ItemsSource, however, if you just use List<>, when you add/remove/reset/clear the collection,
the UI doesn't change as your changes. But ObservableCollection<> supports this. And make sure your binding is for properties but not fields, so that generate your data class using public properties.
In your case, you said that you don't know what the Fields are until your parse the XML string, what do you mean by that? if you don't know the data structure of the data class? If so, it is a hard work, .net framework supports anonymous class, however,
I don't if the <T> class can support it. Thus I think at least you should have a data class type for the IEnumerable<T>. If you can provide this class, everything goes well, you can use LinqToXML to generate your class's instances
and put them in the collection -- ObservableCollection<>, and set it as the ItemsSource of the DataGrid.
And for user administration, I think this is simple in silverlight, maybe you can pass the userid to silverlight app and then determin the user's authority just in silverlight app but not your webservice.
Regards!
Ling Bing
Bei Jing University of Aeronautics and Astronautics
Bei Jing, China
The key idea is to use a value converter (myValuePicker in the above code) that can pick up a value based on different converter parameters. Good luck.
pseudo might be to "high level" for me on this topic, I'm not sure how to use your idea as I have only recently picked up C# while being fluent in PHP web development.
What might help is.. I can work on this issue at the web service level and work with a dataset object before being converted to an xml string. My goal is for the web service to return data as
a type, the DataGrid control can bind to and automatically populate headers.
I didn't want to explain in to much detail, but in reply to lingbing’s posting, what I have is a web service that is getting data from analysis service (OLAP) and the queries within my web service require user arguments
which I pass from my SL app to the service method. The back end of the OLAP source is very complicated but in short the resulted query that is called from my web service works like this…
let’s say the web service passes the following query to the OLAP source < I’ll use sql and not MDX to keep it simple… “ Select * from DatasetA,”
.. Ifthe user is John Doe, because of their access level, the result might be a dataset with FieldB,FieldC,FieldF and FieldG ….. but for Joe Smith the result might be a dataset with FieldA,FieldB,FieldE and FieldI.
To add to the complication, With in the Cube on the backend, DatasetA may have a new field added or removed at any time on the back end.. and Fields a user can see change as well (Promotion, department transfer, new employee, etc…), So what Joe Smith can see
today might be different another day if there department access level changes.
So there is absolutely no way to predefine fields in a collection as a class. So I am stuck with a successful passing of the user from SL to a data interface (My web service) which successfully retrieves a dataset
as a dataset type… in which I am currently converting into an XML string to return back to the SL app ( in hopes of being able to do something with it as SL does not support dataset object)… in which I am currently stuck trying to figure out how to present
the data back to the user in a DataGrid. L
Is there a way that I could use the TypeBuilder Class to create a class during runtime that would logically perform something like this:
Create new public class as MyDataClass
While looping thru each field name -> add new member to MyDataClass as public string with the name of this.fieldname
Then I could construct a list<MyDataClass> and then populate it by traversing the rows within the dataset .... and when finished... destroy the MyDataClass Instance.
Ok ... This looks like a good start.. except.. How do I change the return type in my [WebMethod] function to IEnumerable so that I have a Dictonary feed instead of my current xml string... before I got started working on chaning my output i got the following
when I messed with the function declaration: "Using the generic type 'System.Collections.Generic.IDictionary<TKey,TValue>' requires '2' type arguments"
[WebPart]
Public IEnumerable<IDictionary> myGetDataFunction( etc..... )
Well... like I said.... I'm learning as I go. I just finished looking up and learning more about what types/objects and interfaces are supported with in webservice. Unless I read wrong.. interfaces are not supported due the XML serializer ...
So I'll keep it as an xml string -> within the SL app parse it out to a Dictonary and then use the custom class that creates an IEnumerable of Dictionary...
I was having the same issue. as you have....then i found a article and which i have pasted here....i have implemeted with live data and its works
If you want to define the number and the type of Silverlight DataGrid columns at runtime you can use the following approach. The technique can actually be used not only
for Silverlight but also anywhere where you have to transform IDictionary (for example Dictionary or Hashtable, SortedDictionary etc) into anonymous typed
object with each dictionary key turned into an
object property.
You may say that this way I can violate the
object constraints. For example each IDictionary inside IEnumerable can have different
set of keys. And to a certain degree I can agree with that and
if you rely on the data to come from a third party I wouldn’t recommend
this approach or at least I would advice you to validate each IEnumerable entry before binding it to the control. But
if you have a complete control on the transformation of the data –
this is definitely a good approach. You can think of
this as a way to use C# as a dynamic language. In the current solution I check the keys in the first entry of IEnumerable and if the second has more keys they will be ignored or if it has less the default values will be passed (null, Guid.Empty etc.)
So here is how your code can look. This
is the code behind of an Xaml page:
public partial class Page : UserControl
{ public Page()
{
InitializeComponent();
this.theGrid.Columns.Add( new DataGridTextColumn
{
Header = "ID",
Binding = new Binding("ID")
}); this.theGrid.Columns.Add( new DataGridTextColumn
{
Header = "Name",
Binding = new Binding("Name")
}); this.theGrid.Columns.Add( new DataGridTextColumn
{
Header = "Index",
Binding = new Binding("Index")
}); this.theGrid.Columns.Add( new DataGridTextColumn
{
Header = "Is Even",
Binding = new Binding("IsEven")
}); this.theGrid.ItemsSource = GenerateData().ToDataSource();
}
public IEnumerable GenerateData()
{ for(var i = 0; i < 15; i++)
{
var dict = new Dictionary<string,
object>();
dict["ID"] = Guid.NewGuid();
dict["Name"] = "Name_" + i;
dict["Index"] = i;
dict["IsEven"] = ( i%2 == 0);
yield return dict;
}
}
}Or in Visual Basic:
...
Public Function GenerateData() As IEnumerable(Of IDictionary)
Dim list As New List(Of IDictionary)()
For i As Integer = 0 To 9
Dim dict As IDictionary = New Dictionary(Of String, Object)()
dict("ID") = Guid.NewGuid()
dict("Name") = "Name_" & i.ToString()
dict("Index") = i
dict("IsEven") = (i Mod 2 = 0)
list.Add(dict)
Next
Return list
End Function
As you can see the IEnumerable of IDictionary has no ToDataSource() method so we have to define
this extension method and there is where the magic happens.
using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; using System.Text; using System.Text.RegularExpressions; using System.Windows.Browser;
namespace com.bodurov
{
public static class DataSourceCreator
{ private static readonly Regex PropertNameRegex = new Regex(@"^[A-Za-z]+[A-Za-z1-9_]*$", RegexOptions.Singleline);
private static readonly Dictionary<string, Type> _typeBySigniture =
new Dictionary<string,Type>();
public static IEnumerable ToDataSource(this IEnumerable list)
{
IDictionary firstDict = null; bool hasData = false; foreach (IDictionary currentDict
in list)
{
hasData = true;
firstDict = currentDict; break;
} if (!hasData)
{ return new object[] { };
} if (firstDict == null)
{ throw new ArgumentException("IDictionary entry cannot be null");
}
foreach (DictionaryEntry pair
in firstDict)
{ if (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0))
{
CreateProperty(tb,
Convert.ToString(pair.Key),
GetValueType(pair.Value));
} else
{ throw new ArgumentException(
@"Each key of IDictionary must be
alphanumeric and start with character.");
}
}
objectType = tb.CreateType();
private static Type GetTypeByTypeSigniture(string typeSigniture)
{
Type type; return _typeBySigniture.TryGetValue(typeSigniture,
out type) ? type : null;
}
private static Type GetValueType(object value)
{ return value == null ?
typeof (object) :
value.GetType();
}
grininmonkey
Member
12 Points
9 Posts
Creating dynamic List for binding to Datagrid
Dec 03, 2008 02:02 AM | LINK
I am at wits end and I have seen similar posts asking the same question with no clear answers… I understand that in typical situations you know what fields your looking for and expect from an xml feed or data query via a web service.
However, in my situation, I am working with data in which a datatset contains different columns based on access (not everyone can see the same fields) and user administration… meaning admin users can add new fields or remove existing ones. With that being the case… I can not hard code any list<> objects and define fieldnames within my ASMX web service or SL project as most examples and forums suggest.
So I have a web service that is grabbing data and returning the results as an XML string. I am now trying to figure out how to take that result and bind it to a DataGrid while not knowing what the Fields are until I parse the XML string.
My native language is PHP, however I’m learning C# as best as I can… and this issue has really stumped me. I would “Greatly” appreciate any help on this issue… rather learning how to create dynamic list objects or some other method in which I can parse an XML feed and then create “Something” that the DataGrid can bind with ?
lingbing
Contributor
2249 Points
406 Posts
Re: Creating dynamic List for binding to Datagrid
Dec 03, 2008 03:54 AM | LINK
Hi, the best dynamic list<> is the ObservableCollection<> in namespace System.Collections.ObjectModel. As you know the DataGrid accepts an IEnumerable<> as its ItemsSource, however, if you just use List<>, when you add/remove/reset/clear the collection, the UI doesn't change as your changes. But ObservableCollection<> supports this. And make sure your binding is for properties but not fields, so that generate your data class using public properties.
In your case, you said that you don't know what the Fields are until your parse the XML string, what do you mean by that? if you don't know the data structure of the data class? If so, it is a hard work, .net framework supports anonymous class, however, I don't if the <T> class can support it. Thus I think at least you should have a data class type for the IEnumerable<T>. If you can provide this class, everything goes well, you can use LinqToXML to generate your class's instances and put them in the collection -- ObservableCollection<>, and set it as the ItemsSource of the DataGrid.
And for user administration, I think this is simple in silverlight, maybe you can pass the userid to silverlight app and then determin the user's authority just in silverlight app but not your webservice.
Regards!
Bei Jing University of Aeronautics and Astronautics
Bei Jing, China
codism
Member
372 Points
121 Posts
Re: Creating dynamic List for binding to Datagrid
Dec 03, 2008 05:12 AM | LINK
Let me try to throw some idea here. The following are pseudo code:
The key idea is to use a value converter (myValuePicker in the above code) that can pick up a value based on different converter parameters. Good luck.
vinCracker
Contributor
4059 Points
673 Posts
Re: Creating dynamic List for binding to Datagrid
Dec 03, 2008 05:55 AM | LINK
Hi,
If you want something like this then let me know.
http://silverlight.services.live.com/invoke/81567/Test2/iframe.html
-Vinit
BounceBall - Game in WP7, XNA and Farseer Physics Engine
grininmonkey
Member
12 Points
9 Posts
Re: Creating dynamic List for binding to Datagrid
Dec 03, 2008 02:13 PM | LINK
pseudo might be to "high level" for me on this topic, I'm not sure how to use your idea as I have only recently picked up C# while being fluent in PHP web development.
What might help is.. I can work on this issue at the web service level and work with a dataset object before being converted to an xml string. My goal is for the web service to return data as a type, the DataGrid control can bind to and automatically populate headers.I didn't want to explain in to much detail, but in reply to lingbing’s posting, what I have is a web service that is getting data from analysis service (OLAP) and the queries within my web service require user arguments which I pass from my SL app to the service method. The back end of the OLAP source is very complicated but in short the resulted query that is called from my web service works like this…
let’s say the web service passes the following query to the OLAP source < I’ll use sql and not MDX to keep it simple… “ Select * from DatasetA,” .. If the user is John Doe, because of their access level, the result might be a dataset with FieldB,FieldC,FieldF and FieldG ….. but for Joe Smith the result might be a dataset with FieldA,FieldB,FieldE and FieldI. To add to the complication, With in the Cube on the backend, DatasetA may have a new field added or removed at any time on the back end.. and Fields a user can see change as well (Promotion, department transfer, new employee, etc…), So what Joe Smith can see today might be different another day if there department access level changes.
So there is absolutely no way to predefine fields in a collection as a class. So I am stuck with a successful passing of the user from SL to a data interface (My web service) which successfully retrieves a dataset as a dataset type… in which I am currently converting into an XML string to return back to the SL app ( in hopes of being able to do something with it as SL does not support dataset object)… in which I am currently stuck trying to figure out how to present the data back to the user in a DataGrid. L
grininmonkey
Member
12 Points
9 Posts
Re: Creating dynamic List for binding to Datagrid
Dec 03, 2008 02:28 PM | LINK
Is there a way that I could use the TypeBuilder Class to create a class during runtime that would logically perform something like this:
Create new public class as MyDataClass
While looping thru each field name -> add new member to MyDataClass as public string with the name of this.fieldname
Then I could construct a list<MyDataClass> and then populate it by traversing the rows within the dataset .... and when finished... destroy the MyDataClass Instance.
Is this logically possible in C# ?
vladb
Member
61 Points
12 Posts
Re: Creating dynamic List for binding to Datagrid
Dec 03, 2008 09:51 PM | LINK
http://blog.bodurov.com/How-to-bind-Silverlight-DataGrid-from-IEnumerable-of-IDictionary
grininmonkey
Member
12 Points
9 Posts
Re: Creating dynamic List for binding to Datagrid
Dec 03, 2008 10:17 PM | LINK
Ok ... This looks like a good start.. except.. How do I change the return type in my [WebMethod] function to IEnumerable so that I have a Dictonary feed instead of my current xml string... before I got started working on chaning my output i got the following when I messed with the function declaration: "Using the generic type 'System.Collections.Generic.IDictionary<TKey,TValue>' requires '2' type arguments"
[WebPart]
Public IEnumerable<IDictionary> myGetDataFunction( etc..... )
What should I do on my web service ?
grininmonkey
Member
12 Points
9 Posts
Re: Re: Creating dynamic List<> for binding to Datagrid
Dec 04, 2008 01:16 AM | LINK
Well... like I said.... I'm learning as I go. I just finished looking up and learning more about what types/objects and interfaces are supported with in webservice. Unless I read wrong.. interfaces are not supported due the XML serializer ...
So I'll keep it as an xml string -> within the SL app parse it out to a Dictonary and then use the custom class that creates an IEnumerable of Dictionary...
Raag80
Member
396 Points
91 Posts
Re: Re: Creating dynamic List<> for binding to Datagrid
Mar 13, 2009 12:16 PM | LINK
Hi,
I was having the same issue. as you have....then i found a article and which i have pasted here....i have implemeted with live data and its works
If you want to define the number and the type of Silverlight DataGrid columns at runtime you can use the following approach. The technique can actually be used not only for Silverlight but also anywhere where you have to transform IDictionary (for example Dictionary or Hashtable, SortedDictionary etc) into anonymous typed object with each dictionary key turned into an object property.
You may say that this way I can violate the object constraints. For example each IDictionary inside IEnumerable can have different set of keys. And to a certain degree I can agree with that and if you rely on the data to come from a third party I wouldn’t recommend this approach or at least I would advice you to validate each IEnumerable entry before binding it to the control. But if you have a complete control on the transformation of the data – this is definitely a good approach. You can think of this as a way to use C# as a dynamic language. In the current solution I check the keys in the first entry of IEnumerable and if the second has more keys they will be ignored or if it has less the default values will be passed (null, Guid.Empty etc.)
So here is how your code can look. This is the code behind of an Xaml page:
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
this.theGrid.Columns.Add(
new DataGridTextColumn
{
Header = "ID",
Binding = new Binding("ID")
});
this.theGrid.Columns.Add(
new DataGridTextColumn
{
Header = "Name",
Binding = new Binding("Name")
});
this.theGrid.Columns.Add(
new DataGridTextColumn
{
Header = "Index",
Binding = new Binding("Index")
});
this.theGrid.Columns.Add(
new DataGridTextColumn
{
Header = "Is Even",
Binding = new Binding("IsEven")
});
this.theGrid.ItemsSource = GenerateData().ToDataSource();
}
public IEnumerable GenerateData()
{
for(var i = 0; i < 15; i++)
{
var dict = new Dictionary<string, object>();
dict["ID"] = Guid.NewGuid();
dict["Name"] = "Name_" + i;
dict["Index"] = i;
dict["IsEven"] = ( i%2 == 0);
yield return dict;
}
}
}Or in Visual Basic:
...
Public Function GenerateData() As IEnumerable(Of IDictionary)
Dim list As New List(Of IDictionary)()
For i As Integer = 0 To 9
Dim dict As IDictionary = New Dictionary(Of String, Object)()
dict("ID") = Guid.NewGuid()
dict("Name") = "Name_" & i.ToString()
dict("Index") = i
dict("IsEven") = (i Mod 2 = 0)
list.Add(dict)
Next
Return list
End Function
The Xaml can be as simple as that:
"LayoutRoot" Background="White">
"theGrid"
Grid.Column="0"
Grid.Row="0"
AutoGenerateColumns="False">
As you can see the IEnumerable of IDictionary has no ToDataSource() method so we have to define this extension method and there is where the magic happens.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Browser;
namespace com.bodurov
{
public static class DataSourceCreator
{
private static readonly Regex PropertNameRegex =
new Regex(@"^[A-Za-z]+[A-Za-z1-9_]*$", RegexOptions.Singleline);
private static readonly Dictionary<string, Type> _typeBySigniture =
new Dictionary<string,Type>();
public static IEnumerable ToDataSource(this IEnumerable list)
{
IDictionary firstDict = null;
bool hasData = false;
foreach (IDictionary currentDict in list)
{
hasData = true;
firstDict = currentDict;
break;
}
if (!hasData)
{
return new object[] { };
}
if (firstDict == null)
{
throw new ArgumentException("IDictionary entry cannot be null");
}
string typeSigniture = GetTypeSigniture(firstDict);
Type objectType = GetTypeByTypeSigniture(typeSigniture);
if(objectType == null)
{
TypeBuilder tb = GetTypeBuilder(typeSigniture);
ConstructorBuilder constructor =
tb.DefineDefaultConstructor(
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName);
foreach (DictionaryEntry pair in firstDict)
{
if (PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0))
{
CreateProperty(tb,
Convert.ToString(pair.Key),
GetValueType(pair.Value));
}
else
{
throw new ArgumentException(
@"Each key of IDictionary must be
alphanumeric and start with character.");
}
}
objectType = tb.CreateType();
_typeBySigniture.Add(typeSigniture, objectType);
}
return GenerateEnumerable(objectType, list, firstDict);
}
private static Type GetTypeByTypeSigniture(string typeSigniture)
{
Type type;
return _typeBySigniture.TryGetValue(typeSigniture, out type) ? type : null;
}
private static Type GetValueType(object value)
{
return value == null ? typeof (object) : value.GetType();
}
private static string GetTypeSigniture(IDictionary firstDict)
{
StringBuilder sb = new StringBuilder();
foreach (DictionaryEntry pair in firstDict)
{
sb.AppendFormat("_{0}_{1}", pair.Key, GetValueType(pair.Value));
}
return sb.ToString().GetHashCode().ToString().Replace("-", "Minus");
}
private static IEnumerable GenerateEnumerable(
Type objectType, IEnumerable list, IDictionary firstDict)
{
var listType = typeof(List<>).MakeGenericType(new[] { objectType });
var listOfCustom = Activator.CreateInstance(listType);
foreach (var currentDict in list)
{
if (currentDict == null)
{
throw new ArgumentException("IDictionary entry cannot be null");
}
var row = Activator.CreateInstance(objectType);
foreach (DictionaryEntry pair in firstDict)
{
if (currentDict.Contains(pair.Key))
{
PropertyInfo property =
objectType.GetProperty(Convert.ToString(pair.Key));
property.SetValue(
row,
Convert.ChangeType(
currentDict[pair.Key],
property.PropertyType,
null),
null);
}
}
listType.GetMethod("Add").Invoke(listOfCustom, new[] { row });
}
return listOfCustom as IEnumerable;
}
private static TypeBuilder GetTypeBuilder(string typeSigniture)
{
AssemblyName an = new AssemblyName("TempAssembly" + typeSigniture);
AssemblyBuilder assemblyBuilder =
AppDomain.CurrentDomain.DefineDynamicAssembly(
an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder tb = moduleBuilder.DefineType("TempType" + typeSigniture
, TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout
, typeof(object));
return tb;
}
private static void CreateProperty(
TypeBuilder tb, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName,
propertyType,
FieldAttributes.Private);
PropertyBuilder propertyBuilder =
tb.DefineProperty(
propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr =
tb.DefineMethod("get_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
propertyType, Type.EmptyTypes);
ILGenerator getIL = getPropMthdBldr.GetILGenerator();
getIL.Emit(OpCodes.Ldarg_0);
getIL.Emit(OpCodes.Ldfld, fieldBuilder);
getIL.Emit(OpCodes.Ret);
MethodBuilder setPropMthdBldr =
tb.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new Type[] { propertyType });
ILGenerator setIL = setPropMthdBldr.GetILGenerator();
setIL.Emit(OpCodes.Ldarg_0);
setIL.Emit(OpCodes.Ldarg_1);
setIL.Emit(OpCodes.Stfld, fieldBuilder);
setIL.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
}
}
}
Raag
If it is Solved your problem then mark as answer bcoz other with same question can benefit.....