Dynamic Objects
Posted: Mon Jul 18, 2011 8:05 am
I am currently developing, using the Client Only version of Stimulsoft a webpart to work within SharePoint 2010.
The aim is for the Silverlight webpart to be deployed to any SharePoint Server and the SharePoint List are represented as Business Objects.
This I have achieved but I am unhappy with the solution as it uses DynamicAssembly to create my Business Objects.
This is because I do not know the structure of the SharePoint Lists up front so I have to dynamically build an object that exposes Public Properties for each field in the SharePoint List. This I feel is not an elegant solution. I would prefer to use Dynamics (DynamicMetaObject).
The problem with this approach is that RegBusinessObject({Category}.{Name},{Object}) seems to use reflection to extract the public properties. What I would like it to do is to call a method that will return a list of valid fields and types (PropertyInfo) that it can use.
The second problem I have is performance. Is there a way to page the data (The SharePoint Client Object API allows this) because if the SharePoint List contains several thousand entries I do no need to pull them all to render.
The aim is for the Silverlight webpart to be deployed to any SharePoint Server and the SharePoint List are represented as Business Objects.
This I have achieved but I am unhappy with the solution as it uses DynamicAssembly to create my Business Objects.
Code: Select all
///
/// Creates the data object.
///
/// The dictionary.
/// The name of the data object type.
/// The data object
private static object CreateDataObject(Dictionary dictionary, string dataObjectName)
{
// create a dynamic assembly and module
var assemblyName = new AssemblyName { Name = "tmpAssembly" };
var assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
var module = assemblyBuilder.DefineDynamicModule("tmpModule");
// create a new type builder
var typeBuilder = module.DefineType(
dataObjectName, TypeAttributes.Public | TypeAttributes.Class);
// Loop over the attributes that will be used as the properties names in out new type
foreach (var elementKey in dictionary.Keys)
{
var propertyName = elementKey;
// Generate a private field
var field = typeBuilder.DefineField(propertyName, typeof(string), FieldAttributes.Private);
// Generate a public property
var property = typeBuilder.DefineProperty(propertyName, PropertyAttributes.None, typeof(string), new[] { typeof(string) });
// The property set and property get methods require a special set of attributes:
const MethodAttributes GetSetAttr = MethodAttributes.Public | MethodAttributes.HideBySig;
// Define the "get" accessor method for current private field.
var currGetPropMthdBldr = typeBuilder.DefineMethod("get_value", GetSetAttr, typeof(string), Type.EmptyTypes);
// Intermediate Language stuff...
var currGetIl = currGetPropMthdBldr.GetILGenerator();
currGetIl.Emit(OpCodes.Ldarg_0);
currGetIl.Emit(OpCodes.Ldfld, field);
currGetIl.Emit(OpCodes.Ret);
// Define the "set" accessor method for current private field.
var currSetPropMthdBldr = typeBuilder.DefineMethod("set_value", GetSetAttr, null, new[] { typeof(string) });
// Again some Intermediate Language stuff...
var currSetIl = currSetPropMthdBldr.GetILGenerator();
currSetIl.Emit(OpCodes.Ldarg_0);
currSetIl.Emit(OpCodes.Ldarg_1);
currSetIl.Emit(OpCodes.Stfld, field);
currSetIl.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(currGetPropMthdBldr);
property.SetSetMethod(currSetPropMthdBldr);
}
// Generate our type
var generatedType = typeBuilder.CreateType();
// Now we have our type. Let's create an instance from it:
var generatedObject = Activator.CreateInstance(generatedType);
// Loop over all the generated properties, and assign the values from our XML:
var properties = generatedType.GetProperties();
var propertiesCounter = 0;
// Loop over the values that we will assign to the properties
foreach (var elementValue in dictionary.Values)
{
properties[propertiesCounter].SetValue(generatedObject, elementValue, null);
propertiesCounter++;
}
// Return the new generated object.
return generatedObject;
}
Code: Select all
#region Using Directives
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
#endregion
///
/// Stores Dynamic Metadata.
///
public class MetaData : DynamicObject
{
private List propertInfo = new List();
#region Constants and Fields
///
/// Stores the Dictionary
///
private readonly IDictionary dictionary;
#endregion
#region Constructors and Destructors
///
/// Initializes a new instance of the class.
///
///
/// The dictionary.
///
public MetaData(IDictionary dictionary)
{
this.dictionary = dictionary;
}
///
/// Enables derived types to initialize a new instance of the type.
///
///
public MetaData()
{
this.dictionary = new Dictionary();
}
#endregion
#region Public Overload Methods
///
/// If a property value is a delegate, invoke it
///
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = null;
return false;
}
///
/// Provides the implementation for operations that get member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as getting a value for a property.
///
/// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member on which the dynamic operation is performed. For example, for the Console.WriteLine(sampleObject.SampleProperty) statement, where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.
/// The result of the get operation. For example, if the method is called for a property, you can assign the property value to .
///
/// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a run-time exception is thrown.)
///
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var name = binder.Name;
// If the property name is found in a dictionary,
// set the result parameter to the property value and return true.
// Otherwise, return false.
return dictionary.TryGetValue(name, out result);
}
///
/// Provides the implementation for operations that set member values. Classes derived from the class can override this method to specify dynamic behavior for operations such as setting a value for a property.
///
/// Provides information about the object that called the dynamic operation. The binder.Name property provides the name of the member to which the value is being assigned. For example, for the statement sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, binder.Name returns "SampleProperty". The binder.IgnoreCase property specifies whether the member name is case-sensitive.
/// The value to set to the member. For example, for sampleObject.SampleProperty = "Test", where sampleObject is an instance of the class derived from the class, the is "Test".
/// true if the operation is successful; otherwise, false. If this method returns false, the run-time binder of the language determines the behavior. (In most cases, a language-specific run-time exception is thrown.)
///
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
// You can always add a value to a dictionary,
// so this method always returns true.
return true;
}
#endregion
#region Public Methods
///
/// Gets the fields.
///
/// A collections of filed
///
public ICollection GetFields()
{
return this.dictionary.Keys;
}
///
/// Gets the value.
///
/// The field.
/// Business Object Value
///
public object GetValue(string field)
{
return this.dictionary.ContainsKey(field) ? this.dictionary[field] : null;
}
#endregion
Code: Select all
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
Code: Select all
var data2 = new List{new Person{ Name = "Max", Age = 18 },new Person { Name = "Dave", Age = 23 }};
dynamic d1 = new MetaData();
d1.Name = "Max";
d1.Age = 18;
dynamic d2 = new MetaData();
d2.Name = "Dave";
d2.Age = 23;
var data = new List { d1, d2 };
this.stiSLViewerControl1.Report.RegBusinessObject("Test","PersonDynamic",data); // Does not work
this.stiSLViewerControl1.Report.RegBusinessObject("Test", "PersonBusinessObject", data2); //Works
The second problem I have is performance. Is there a way to page the data (The SharePoint Client Object API allows this) because if the SharePoint List contains several thousand entries I do no need to pull them all to render.