Databinding to Dynamic Objects

by Bron Skinner 17. April 2011 10:47

A recent task at work required that I take some data formatted in a less than friendly way and provide an interface to manipulate and save the data. Since our application is built with Silverlight, building a UI and using data binding is the way to go. The problem though, is that the data itself is dynamic based on the orginization consuming the application. This raises the question, how can you build a UI structured around a dynamic business object?

My first inclination was to use the DynamicObject in the System.Dynamic namespace. In looping through the data obtaind from our database, I could construct an object and controltemplate that would reflect the dynamic properties/values interpreted post compilation. However, to my dismay the DynamicObject does not currently work correctly in SL4 but Microsoft has made posts that this is on their radar, perhaps in SL5?

In doing a little research I came across a pretty cool solution that I won't try to take credit for, but did modify slightly to work under our specific scenario, and it works like a charm. You can see the original article here. Essentially we're creating a wrapper class that inherits from DynamicObject and INotifyPropertyChanged. Specifically, we end up storing the added members in a dictionary<string, object> much like the ExpandoObject, and provide an indexer for the object that lets us set and get these dynamic properties. Finally, we override the TryGetMember and TrySetMember methods implemented in the DynamicObject, which let's us use dynamic expressions in code.    In code we create a new ElasticObject, specify properties and values, then set our binding. Note that when specifying the PropertyPath in the binding, brackets are needed to indicate we're referencing the object's indexer. 

    public class ElasticObject : DynamicObject, INotifyPropertyChanged

    {

        public event PropertyChangedEventHandler PropertyChanged;

        private readonly Dictionary<string, object> properties = new System.Collections.Generic.Dictionary<string, object>();

 

        public object this[string name]

        {

            get

            {

                object result;

                properties.TryGetValue(name, out result);

                return result;

            }

            set

            {

                properties[name] = value;

                RaisePropertyChanged(name);

            }

        }

 

        public override bool TryGetMember(GetMemberBinder binder, out object result)

        {

            return properties.TryGetValue(binder.Name, out result);

        }

 

        /// <summary>

        /// Specify Var as binder.Name and pass a KeyValuePair<string, object> where string is the dyanmic propertyname to use

        /// and object the value.

        /// </summary>

        /// <param name="binder"></param>

        /// <param name="value"></param>

        /// <returns></returns>

        public override bool TrySetMember(SetMemberBinder binder, object value)

        {

            if (binder.Name.ToLower().StartsWith("var"))

            {

                try

                {

                    if (value != null && value.GetType() == typeof(KeyValuePair<string, object>))

                    {

                        properties[((KeyValuePair<string, object>)value).Key] = ((KeyValuePair<string, object>)value).Value;

                    }

                }

                catch

                {

                    return false;

                }

            }

            else

            {

                properties[binder.Name] = value;

                RaisePropertyChanged(binder.Name);

            }

            return true;

        }

 

        private void RaisePropertyChanged(string propertyName)

        {

            if (PropertyChanged != null)

                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));

        }

    }

 

 

dynamic o = new ElasticObject();

o.Name = "FooBar";

o.var = new KeyValuePair<string, object>("VariablePropertyName", new object());

txt_MyTextBox.SetBinding(TextBox.TextProperty, new Binding("[VariablePropertyName]") { Mode = BindingMode.TwoWay });

txt_MyTextBox.DataContext = o;

 

Now one requirment I had beyond providing dynamic objects and binding, was to provide a means of specifying dynamic property names as well. The solution I came up, well we'll just call it creative and leave it at that. I modified the TrySetMember function to look for input property names that equal 'var', and then expect a keyvaluepair<string, objrect> as the passed propertyvalue containing you guessed it, the dynamic proprety name to use (Key) and propertyvalue (Value).

 

Tags:

Dynamic LINQ to SQL?

by Bron Skinner 15. February 2011 22:20

Alright - use the information gathered from this post at your own risk as I don't condone this solution as ironclad, but do find it interesting enough to post.

Recently I've been working on a module for an app that uses Linq to SQL within a WCF Service to update values in a database. The app itself in short, is a reporting app and my task is to take validated data from the client-side (another part of the project), and store it. To do this I've mapped the given database schema within a DBML and have provided a Complex Type Report Object definition via WSDL which is expected as the first argument in a majority of the service's exposed CRUD functions.

I could have just manually mapped Service Report Object values to those exposed via the DBML, but given my normal nature I've decided to take the entire setup a bit further and build a set of functions that allow for dynamic getting and setting of values without relying on manual mapping. To justify this - like all applications, especially those that may be used by multiple organizations, requirements undoubtedly change and I’d really rather not have go through an intricate and lengthy process to update service definitions/references, redeployment, etc.

To do this, I needed to find a way to dynamically read incoming properties within our dbml context, and our report service object. As well, I needed a way to dynamically build/represent our report definition.

Report Definition

In a table in SQL we store the properties/property types/indexes of our properties we want to expose via our report. An external app could be built to manage this (among other) stored complex type definitions. Note we could also simply store our wsdl xml in the database instead. A static Report object in our app WCF Service has single public property, a Dictionary of Key Value Pairs. These values will need to be populated within the consuming application with our SQL stored properties, and Keys with the property names. Note that DataType information is essential in our SQL definition when initializing our Value objects as we rely on Value Type defaults when actually interrogating/assigning values to our properties.

The Logic

After the consuming application has downloaded the WCF Service object definition and SQL Report object property data, and then populated/assigned these values, it passes the report as an argument to the WCF Service. For each CRUD function (Create/Update/Read but not Delete), we rely on a similar set of function calls. We loop through recursively each property in our report, interrogate the PropertyInfo for each and attempt to match to a recursive property search on our matching report object (If we’re updating we get values from our WCF Report object and match to our DBML context, and if we’re setting we reverse that order => DBML Context to WCF Report object.

A check on each DBML Context property for Identity/DBType/IsDBGenerated/Non Null allows our logic to correctly ignore/assign values/default values and prevent sql exceptions when we call our context.submit(). A couple example functions (set/get/default value methods):

private void SetPropertyValues<TLocal, TContext>(TLocal l, ref TContext c)

        {

            if (l != null && c != null)

            {

                foreach (PropertyInfo cp in c.GetType().GetProperties())

                {

                    try

                    {

                        bool isIdentity = false;

                        bool isNonNull = false;

                        bool isDbGenerated = false;

 

                        foreach (CustomAttributeData attr in cp.GetCustomAttributesData())

                        {

                            foreach (CustomAttributeNamedArgument arg in attr.NamedArguments)

                            {

                                if (arg.TypedValue != null)

                                {

                                    if (arg.TypedValue.Value.ToString().ToLower().Contains("not null"))

                                    {

                                        isNonNull = true;

                                    }

                                    if (arg.TypedValue.Value.ToString().ToLower().Contains("identity"))

                                    {

                                        isIdentity = true;

                                    }

                                    if (arg.MemberInfo.Name.ToLower().Contains("isdbgenerated"))

                                    {

                                        isDbGenerated = true;

                                    }

                                    if (isNonNull && isIdentity && isDbGenerated)

                                    {

                                        break;

                                    }

                                }

                            }

                        }

               

                        if (!isDbGenerated)

                        {

                            PropertyInfo lp = l.GetType().GetProperty(cp.Name);

                            if (lp != null && lp.PropertyType == cp.PropertyType) cp.SetValue(c, lp.GetValue(GetDefaultValue(l, lp.PropertyType), null), null);

                            else

                            {

                                if (isNonNull)

                                {

                                    cp.SetValue(c, GetDefaultValue(cp.GetValue(c, null), cp.PropertyType), null);

                                }

                            }

                        }

                    }

                    catch

                    {

                        //error handling

                    }

                }

            }

        }

 

private void GetPropertyValues<TLocal, TContext>(ref TLocal l, TContext c)

        {

            if (l != null && c != null)

            {

                foreach (PropertyInfo cp in c.GetType().GetProperties())

                {

                    try

                    {

                        PropertyInfo lp = l.GetType().GetProperty(cp.Name);

                        if (lp != null) lp.SetValue(l, GetDefaultValue(cp.GetValue(c, null), cp.PropertyType), null);

                    }

                    catch

                    {

                        //error handling

                    }  

                }

            }

        }

 

private T GetDefaultValue<T>(object value)

        {

            return value == DBNull.Value ? default(T) : (T)value;

        }

 

        private object GetDefaultValue(object value, Type targetType)

        {

            object defaultValue = targetType.IsValueType ? Activator.CreateInstance(targetType) : null;

            #region Custom Defaults...

            if (targetType == typeof(string)) defaultValue = string.Empty;

            if (targetType == typeof(DateTime) && (DateTime)value == DateTime.MinValue) defaultValue = DateTime.Now;

            #endregion

            return GetDefaultValue<object>(value) ?? defaultValue;

        }

 

 

Overlooked

Now one thing I’ve not yet figured out is how to dynamically update our DBML, which rather makes all of this moot now doesn’t it? Perhaps a better way of doing this is to instead of interrogating a DBML context object for matching properties is to use a generic collection of DataSet as our context.

 

 

Tags:

Technical

Powered by BlogEngine.NET 1.5.0.7
Custom theme for digitalboon.com by Bron Skinner

About the author

A Software Developer with a keen artistic sense, I’ve spent the last couple years working with predominantly Microsoft-based technologies developing web applications. The majority of this time has been spent building applications with SilverlightTM that forward some rather unique approaches to interface design. I am currently working full time.

 

 

Make sure to check out The Forge.


Download Resume