Page 1 of 2

Dynamic Objects

Posted: Mon Jul 18, 2011 8:05 am
by MtheP
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.

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;
        }
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).

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
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.

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.

Dynamic Objects

Posted: Tue Jul 19, 2011 2:39 am
by Alex K.
Hello,

Unfortunately, on current moment our product not supported SharePoint. And we do not have plans to develop this feature in the near time. Sorry.

Thank you.

Dynamic Objects

Posted: Tue Jul 19, 2011 5:44 am
by MtheP
The question has nothing to do with SharePoint it has to do with supporting dynamics as business objects and if you can implement paging for the business objects. For the record your solution actually works quite well with SharePoint

Dynamic Objects

Posted: Wed Jul 20, 2011 11:36 am
by Andrew
Hello,

Unfortunately, we cannot test your example.
You use classes of SharePoint.
We do not have the SharePoint, so we cannot compile and test your sample. Sorry.

Also, work with dynamic objects is very complex.
It is often impossible to obtain data from many dynamic objects due to some internal strange bugs of the .Net Framework.
Maybe in the future a Service Pack for the .Net Framework will fix this problem.

Thank you.

Dynamic Objects

Posted: Thu Jul 28, 2011 5:54 am
by JaSioo
Hello,

I've managed to fill data from my dictionary source (somehow related to dynamics), but the problem is that data band shows only first row. but event (StiText.GetValue) on which I operate filled every one with correct data. What should I change in this sample project so everything will be rendered.

Code: Select all


namespace SilverlightApplication1
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        private ExpandoObject _columnListType = new ExpandoObject();
        StiReport _report = new StiReport();
        private const double _minWidthOfColumn = 3;
        private const string _objectName = "DynamicObjects";


        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            List> lista = new List>();
            for (int i = 0; i  dic = new Dictionary();
                for (int j = 0; j >).Count;
            dataBand.Height = 0.5;
            dataBand.Name = "DataBand";// +skip.ToString();
            //dataBand.DataSourceName = "DynamicObjects";
            dataBand.BusinessObjectGuid = _report.Dictionary.BusinessObjects[_objectName].Guid;
            page.Components.Add(dataBand);
            int index = 0;
            foreach (var member in ((IDictionary)_columnListType))
            {
                //Create text on header
                StiText hText = new StiText(new RectangleD(pos, 0, columnWidth, 0.5));
                hText.Text.Value = member.Key;
                hText.HorAlignment = StiTextHorAlignment.Center;
                hText.Name = "HeaderText" + index.ToString();
                hText.Brush = new StiSolidBrush(System.Windows.Media.Colors.Orange);
                hText.Border.Side = StiBorderSides.All;
                hText.WordWrap = true;
                hText.CanGrow = true;
                headerBand.Components.Add(hText);

                // Create data binding to data boxes
                StiText dataText = new StiText(new RectangleD(pos, 0, columnWidth, 0.5));
                //dataText.Text = "{" + string.Format("{1}.{0}", member.Key.ToString(), _objectName) + "}";
                dataText.Text = "{" + string.Format("GetValueDictionaryBusinessObject(\"{0}\", \"{1}\")", _objectName, member.Key) + "}";
                dataText.Name = "DataText" + index.ToString();
                dataText.Border.Side = StiBorderSides.All;
                dataText.WordWrap = true;
                dataText.CanGrow = true;
                dataText.GrowToHeight = true;
                dataText.GetValue += new Stimulsoft.Report.Events.StiGetValueEventHandler(dataText_GetValue);
                dataBand.Components.Add(dataText);
                pos = pos + columnWidth;
                index++;
            }

        }

        void dataText_GetValue(object sender, Stimulsoft.Report.Events.StiGetValueEventArgs e)
        {
            if (sender is StiText)
            {
                StiText txtBX = sender as StiText;
                string previous = txtBX.Text;
                int lineNr = _report.Line;
                int indexBegin = previous.IndexOf("GetValueDictionaryBusinessObject") + 34;
                if (indexBegin >)
            {
                if (_report.Line >).Count)
                {
                    returnString = (_report.Dictionary.BusinessObjects[nameOfBO].BusinessObjectValue as List>)[_report.Line - 1][((IDictionary)_columnListType)[nameOfKey].ToString()];
                }
            }

            return returnString;
        }

        public StiReport LoadReport(List> lo)
        {
            int cntr = 0;
            foreach (var i in lo[0])
            {
                ((IDictionary)_columnListType).Add(i.Key, i.Value);
                cntr++;
            }

            cntr = 0;
            List> listVars = new List>();
            foreach (var item in lo)
            {
                if (cntr > 0)
                {
                    listVars.Add(item.ToDictionary(k => k.Key, v => v.Value));
                }
                cntr++;
            }
            Stimulsoft.Report.Dictionary.StiBusinessObjectData bo = new Stimulsoft.Report.Dictionary.StiBusinessObjectData("LocalBOs", _objectName, listVars);

            _report.DataSources.Clear();
            _report.BusinessObjectsStore.Clear();

            _report.RegBusinessObject("LocalBOs", _objectName, _objectName, listVars);

            _report.Dictionary.SynchronizeBusinessObjects();
            _report.Dictionary.Synchronize();

            if (_report.Dictionary.BusinessObjects[_objectName].Columns.Count )_columnListType))
                {
                    repBO.Columns.Add(col.Key, col.Key, col.Value.GetType());
                }
            }

            _report.Dictionary.SynchronizeBusinessObjects();
            _report.Dictionary.Synchronize();

            StiPage pager = _report.Pages[0];

            int count = GetColumnsCount(_columnListType);
            double columnWidth = (pager.Width / count)  colsPerPage)
                {
                    pager.SegmentPerWidth = (int)System.Math.Ceiling(count / colsPerPage);
                }

            }

            CreateReportLocaly(pager, columnWidth);

            _report.ReportName = "Lista: ";

            _report.Dictionary.SynchronizeBusinessObjects();
            _report.Dictionary.Synchronize();
            _report.Render();

            return _report;
        }

        private static int GetColumnsCount(ExpandoObject colList)
        {
            return ((IDictionary)colList).Count;
        }
    }
}

Best Regards,
Krzysztof

Dynamic Objects

Posted: Mon Aug 01, 2011 1:17 am
by Alex K.
Hello,

We made some improvements in that direction.
Please check the build from 28-07-2010

Thank you.

Dynamic Objects

Posted: Mon Aug 01, 2011 3:48 am
by JaSioo
Hello,

I've checked this build, but nothing changed in running my sample. Could you please tell me what is new to be used in my sample, so this will show all data?
Thank you.

Best Regards,
Krzysztof

Dynamic Objects

Posted: Thu Aug 04, 2011 1:51 am
by Alex K.
Hello,

Sorry for the delay with response.
In the Silverlight and .Net version the GetValue() event did not work correctly for a text component in the interpretation mode. It has been fixed in the latest prerelease build. To work correctly, please adjust the following line in the dataText_GetValue method:

Code: Select all

e.StoreToPrinted = false;
Thank you.

Dynamic Objects

Posted: Thu Aug 04, 2011 6:38 am
by JaSioo
Aleksey wrote:Hello,

Sorry for the delay with response.
In the Silverlight and .Net version the GetValue() event did not work correctly for a text component in the interpretation mode. It has been fixed in the latest prerelease build. To work correctly, please adjust the following line in the dataText_GetValue method:

Code: Select all

e.StoreToPrinted = false;
Thank you.
Hello,

I've changed this line and it works the same even if it is 'true' or 'false'. I've installed Stimulsoft Reports.Silverlight 2011.07.28 Trial (win7 x64) by overwriting released installation of 2011.1. Report is created on silverlight client only. Could you use my sample project (posted here in previous post) and post it here after modifications that works? Maybe I did something wrong with your dll's?
I would be very grateful.

Thank you.


PS: it sometimes happens that businessobjectvalue is null so i needed to make a check for that. that wasn't in released version 2011.1.

Code: Select all

void dataText_GetValue(object sender, Stimulsoft.Report.Events.StiGetValueEventArgs e)
        {
            if (sender is StiText)
            {
                StiText txtBX = sender as StiText;
                string previous = txtBX.Text;
                int lineNr = _report.Line;
                int indexBegin = previous.IndexOf("GetValueDictionaryBusinessObject") + 34;
                if (indexBegin >)   >).Count)
                {
                    returnString = (_report.Dictionary.BusinessObjects[nameOfBO].BusinessObjectValue as List>)[_report.Line - 1][((IDictionary)_columnListType)[nameOfKey].ToString()];
                }
            }

            return returnString;
        }

Dynamic Objects

Posted: Thu Aug 04, 2011 8:21 am
by Alex K.
Hello,

Please check the modified project and .mdc file in attachment.
PS: it sometimes happens that businessobjectvalue is null so i needed to make a check for that. that wasn't in released version 2011.1.
We couldn't reproduce this bug. Could you explain your issue in more details and, if possible, send us a step-by-step guide how to reproduce the issue?

Thank you.