namespace System.Workflow.ComponentModel { #region Using directives using System; using System.IO; using System.Xml; using System.Text; using System.CodeDom; using System.Reflection; using System.Xml.XPath; using System.Collections; using System.Xml.Serialization; using System.Collections.Generic; using System.ComponentModel; using System.Collections.Specialized; using System.ComponentModel.Design; using System.ComponentModel.Design.Serialization; using System.Diagnostics; using System.Globalization; using System.Drawing.Design; using System.Workflow.ComponentModel.Compiler; using System.Workflow.ComponentModel.Serialization; using System.Workflow.ComponentModel.Design; using System.Configuration; #endregion #region Bind [Browsable(false)] internal abstract class BindBase { [NonSerialized] protected bool designMode = true; [NonSerialized] private object syncRoot = new object(); public abstract object GetRuntimeValue(Activity activity); public abstract object GetRuntimeValue(Activity activity, Type targetType); public abstract void SetRuntimeValue(Activity activity, object value); protected virtual void OnRuntimeInitialized(Activity activity) { } } #endregion #region Redundant Binds #region MemberBind // internal abstract class MemberBind : BindBase { private string name = string.Empty; protected MemberBind() { } protected MemberBind(string name) { this.name = name; } [DefaultValue("")] public string Name { get { return this.name; } } internal static object GetValue(MemberInfo memberInfo, object dataContext, string path) { if (memberInfo == null) throw new ArgumentNullException("memberInfo"); if (dataContext == null) throw new ArgumentNullException("dataContext"); if (path == null) path = string.Empty; if (string.IsNullOrEmpty(path)) return null; object targetObject = dataContext; System.Type memberType = dataContext.GetType(); PathWalker pathWalker = new PathWalker(); pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs) { if (targetObject == null) { eventArgs.Action = PathWalkAction.Cancel; return; } switch (eventArgs.MemberKind) { case PathMemberKind.Field: memberType = (eventArgs.MemberInfo as FieldInfo).FieldType; targetObject = (eventArgs.MemberInfo as FieldInfo).GetValue(targetObject); break; case PathMemberKind.Event: EventInfo evt = eventArgs.MemberInfo as EventInfo; memberType = evt.EventHandlerType; // GetValue() returns the actual value of the property. We need the Bind object here. // Find out if there is a matching dependency property and get the value throw the DP. DependencyObject dependencyObject = targetObject as DependencyObject; DependencyProperty dependencyProperty = DependencyProperty.FromName(evt.Name, dependencyObject.GetType()); if (dependencyProperty != null && dependencyObject != null) { if (dependencyObject.IsBindingSet(dependencyProperty)) targetObject = dependencyObject.GetBinding(dependencyProperty); else targetObject = dependencyObject.GetHandler(dependencyProperty); } else targetObject = null; // eventArgs.Action = PathWalkAction.Stop; break; case PathMemberKind.Property: memberType = (eventArgs.MemberInfo as PropertyInfo).PropertyType; if (!(eventArgs.MemberInfo as PropertyInfo).CanRead) { eventArgs.Action = PathWalkAction.Cancel; return; } targetObject = (eventArgs.MemberInfo as PropertyInfo).GetValue(targetObject, null); break; case PathMemberKind.IndexedProperty: memberType = (eventArgs.MemberInfo as PropertyInfo).PropertyType; if (!(eventArgs.MemberInfo as PropertyInfo).CanRead) { eventArgs.Action = PathWalkAction.Cancel; return; } targetObject = (eventArgs.MemberInfo as PropertyInfo).GetValue(targetObject, eventArgs.IndexParameters); break; case PathMemberKind.Index:// memberType = (eventArgs.MemberInfo as PropertyInfo).PropertyType; targetObject = (eventArgs.MemberInfo as PropertyInfo).GetValue(targetObject, BindingFlags.GetProperty, null, eventArgs.IndexParameters, CultureInfo.InvariantCulture); break; } if (targetObject == null) { if (eventArgs.LastMemberInThePath) { eventArgs.Action = PathWalkAction.Cancel; return; } else { throw new InvalidOperationException(SR.GetString(SR.Error_BindPathNullValue, eventArgs.Path)); } } }; if (pathWalker.TryWalkPropertyPath(memberType, path)) { //success return ((targetObject != dataContext) ? targetObject : null); } else { //failure return null; } } internal static void SetValue(object dataContext, string path, object value) { if (dataContext == null) throw new ArgumentNullException("dataContext"); if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path"); object parentObj = null; object obj = dataContext; object[] args = null; MemberInfo memberInfo = null; PathWalker pathWalker = new PathWalker(); pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs) { // if (obj == null) { eventArgs.Action = PathWalkAction.Cancel; return; } parentObj = obj; memberInfo = eventArgs.MemberInfo; switch (eventArgs.MemberKind) { case PathMemberKind.Field: obj = (eventArgs.MemberInfo as FieldInfo).GetValue(parentObj); args = null; break; case PathMemberKind.Event: // eventArgs.Action = PathWalkAction.Cancel; //set value is not supported on events return; case PathMemberKind.Property: obj = (eventArgs.MemberInfo as PropertyInfo).GetValue(parentObj, null); args = null; break; case PathMemberKind.IndexedProperty: case PathMemberKind.Index: obj = (eventArgs.MemberInfo as PropertyInfo).GetValue(parentObj, eventArgs.IndexParameters); args = eventArgs.IndexParameters; break; } }; if (pathWalker.TryWalkPropertyPath(dataContext.GetType(), path)) { //at this point the 'obj' holds the old value, we will be changing it to 'value' //success if (memberInfo is FieldInfo) { (memberInfo as FieldInfo).SetValue(parentObj, value); } else if (memberInfo is PropertyInfo) { if ((memberInfo as PropertyInfo).CanWrite) (memberInfo as PropertyInfo).SetValue(parentObj, value, args); else throw new InvalidOperationException(SR.GetString(SR.Error_ReadOnlyField, memberInfo.Name)); } } } internal static ValidationError ValidateTypesInPath(Type srcType, string path) { ValidationError error = null; if (srcType == null) throw new ArgumentNullException("srcType"); if (path == null) throw new ArgumentNullException("path"); if (path.Length == 0) throw new ArgumentException(SR.GetString(SR.Error_EmptyPathValue), "path"); Debug.Assert(WorkflowCompilationContext.Current != null, "Can't have checkTypes set to true without a context in scope"); IList authorizedTypes = WorkflowCompilationContext.Current.GetAuthorizedTypes(); if (authorizedTypes == null) { return new ValidationError(SR.GetString(SR.Error_ConfigFileMissingOrInvalid), ErrorNumbers.Error_ConfigFileMissingOrInvalid); } Type propertyType = srcType; MemberInfo memberInfo = null; PathWalker pathWalker = new PathWalker(); pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs) { Type memberType = null; memberInfo = eventArgs.MemberInfo; if (memberInfo is FieldInfo) memberType = ((FieldInfo)memberInfo).FieldType; if (memberInfo is PropertyInfo) memberType = ((PropertyInfo)memberInfo).PropertyType; if (memberType != null && !SafeType(authorizedTypes, memberType)) { error = new ValidationError(SR.GetString(SR.Error_TypeNotAuthorized, memberType), ErrorNumbers.Error_TypeNotAuthorized); eventArgs.Action = PathWalkAction.Stop; return; } }; pathWalker.TryWalkPropertyPath(propertyType, path); return error; } private static bool SafeType(IList authorizedTypes, Type referenceType) { bool authorized = false; foreach (AuthorizedType authorizedType in authorizedTypes) { if (authorizedType.RegularExpression.IsMatch(referenceType.AssemblyQualifiedName)) { authorized = (String.Compare(bool.TrueString, authorizedType.Authorized, StringComparison.OrdinalIgnoreCase) == 0); if (!authorized) return false; } } return authorized; } internal static MemberInfo GetMemberInfo(Type srcType, string path) { if (srcType == null) throw new ArgumentNullException("srcType"); if (path == null) throw new ArgumentNullException("path"); if (path.Length == 0) throw new ArgumentException(SR.GetString(SR.Error_EmptyPathValue), "path"); Type propertyType = srcType; MemberInfo memberInfo = null; PathWalker pathWalker = new PathWalker(); pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs) { memberInfo = eventArgs.MemberInfo; if (eventArgs.MemberKind == PathMemberKind.Event) { //need to exit!!! eventArgs.Action = PathWalkAction.Stop; return; } }; if (pathWalker.TryWalkPropertyPath(propertyType, path)) return memberInfo; else return null; } } #endregion #region FieldBind [ActivityValidator(typeof(FieldBindValidator))] internal sealed class FieldBind : MemberBind { private string path = string.Empty; public FieldBind() { } public FieldBind(string name) : base(name) { } public FieldBind(string name, string path) : base(name) { this.path = path; } public string Path { get { return this.path; } set { if (!this.designMode) throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime)); this.path = value; } } public override object GetRuntimeValue(Activity activity, Type targetType) { throw new NotImplementedException(); } public override object GetRuntimeValue(Activity activity) { throw new NotImplementedException(); } public override void SetRuntimeValue(Activity activity, object value) { throw new NotImplementedException(); } protected override void OnRuntimeInitialized(Activity activity) { throw new NotImplementedException(); } } #endregion #region PropertyBind [ActivityValidator(typeof(PropertyBindValidator))] internal sealed class PropertyBind : MemberBind { private string path = string.Empty; public PropertyBind() { } public PropertyBind(string name) : base(name) { } public PropertyBind(string name, string path) : base(name) { this.path = path; } public string Path { get { return this.path; } set { if (!this.designMode) throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime)); this.path = value; } } public override object GetRuntimeValue(Activity activity, Type targetType) { throw new NotImplementedException(); } public override object GetRuntimeValue(Activity activity) { throw new NotImplementedException(); } public override void SetRuntimeValue(Activity activity, object value) { throw new NotImplementedException(); } } #endregion #region MethodBind [ActivityValidator(typeof(MethodBindValidator))] internal sealed class MethodBind : MemberBind { public MethodBind() { } public MethodBind(string name) : base(name) { } public override object GetRuntimeValue(Activity activity, Type targetType) { throw new NotImplementedException(); } public override object GetRuntimeValue(Activity activity) { throw new Exception(SR.GetString(SR.Error_NoTargetTypeForMethod)); } public override void SetRuntimeValue(Activity activity, object value) { throw new Exception(SR.GetString(SR.Error_MethodDataSourceIsReadOnly)); } } #endregion #endregion #region ActivityBind internal enum ActivityBindTypes { Field = 1, Property = 2, Method = 3 }; [Browsable(true)] [TypeConverter(typeof(ActivityBindTypeConverter))] [ActivityValidator(typeof(ActivityBindValidator))] [DesignerSerializer(typeof(BindMarkupExtensionSerializer), typeof(WorkflowMarkupSerializer))] [Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")] public sealed class ActivityBind : MarkupExtension, IPropertyValueProvider { #region stuff from the former Bind [NonSerialized] private bool designMode = true; [NonSerialized] private bool dynamicUpdateMode = false; [NonSerialized] private IDictionary userData = null; [NonSerialized] private object syncRoot = new object(); internal void SetContext(Activity activity) { this.designMode = false; OnRuntimeInitialized(activity); } internal bool DynamicUpdateMode { get { return this.dynamicUpdateMode; } set { this.dynamicUpdateMode = false; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] private bool DesignMode { get { return this.designMode && !this.dynamicUpdateMode; } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public IDictionary UserData { get { if (this.userData == null) { lock (this.syncRoot) { if (this.userData == null) this.userData = Hashtable.Synchronized(new Hashtable()); } } return this.userData; } } internal static object GetDataSourceObject(Activity activity, string inputName, out string name) { if (activity == null) throw new ArgumentNullException("activity"); if (string.IsNullOrEmpty(inputName)) throw new ArgumentNullException("inputName"); Activity contextActivity = Helpers.GetDataSourceActivity(activity, inputName, out name); return contextActivity; } #endregion private string id = string.Empty; private string path = string.Empty; public ActivityBind() { } public ActivityBind(string name) { this.id = name; } public ActivityBind(string name, string path) { this.id = name; this.path = path; } [DefaultValue("")] [SRDescription(SR.ActivityBindIDDescription)] [ConstructorArgument("name")] public string Name { get { return this.id; } set { if (!this.DesignMode) throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime)); this.id = value; } } [DefaultValue("")] [SRDescription(SR.ActivityBindPathDescription)] [TypeConverter(typeof(ActivityBindPathTypeConverter))] public string Path { get { return this.path; } set { if (!this.DesignMode) throw new InvalidOperationException(SR.GetString(SR.Error_CanNotChangeAtRuntime)); this.path = value; } } public override object ProvideValue(IServiceProvider provider) { return this; } public object GetRuntimeValue(Activity activity, Type targetType) { if (activity == null) throw new ArgumentNullException("activity"); if (targetType == null) throw new ArgumentNullException("targetType"); return this.InternalGetRuntimeValue(activity, targetType); } public object GetRuntimeValue(Activity activity) { if (activity == null) throw new ArgumentNullException("activity"); return this.InternalGetRuntimeValue(activity, null); } private object InternalGetRuntimeValue(Activity activity, Type targetType) { object runtimeValue = null; Activity referencedActivity = Helpers.ParseActivityForBind(activity, this.Name); if (referencedActivity != null) { //Now lets get the MemberInfo MemberInfo memberInfo = ActivityBind.GetMemberInfo(referencedActivity.GetType(), Path, targetType); if (memberInfo != null) { runtimeValue = ActivityBind.GetMemberValue(referencedActivity, memberInfo, Path, targetType); if (runtimeValue is ActivityBind && BindHelpers.GetMemberType(memberInfo) != typeof(ActivityBind)) runtimeValue = ((ActivityBind)runtimeValue).GetRuntimeValue(referencedActivity, targetType); } else { // The value of this ActivityBind is bound to properties or events defined on the referenced activity // Note that we don't have corresponding logic for SetRuntimeValue because value should be only set // at the end of the activity reference chain. Activity rootActivity = Helpers.GetRootActivity(activity); DependencyProperty dependencyProperty = DependencyProperty.FromName(this.Path, rootActivity.GetType()); } } return runtimeValue; } public void SetRuntimeValue(Activity activity, object value) { if (activity == null) throw new ArgumentNullException("activity"); Activity referencedActivity = Helpers.ParseActivityForBind(activity, this.Name); if (referencedActivity != null) { MemberInfo memberInfo = ActivityBind.GetMemberInfo(referencedActivity.GetType(), Path, null); if (memberInfo != null) { ActivityBind bind = ActivityBind.GetMemberValue(referencedActivity, memberInfo, Path, null) as ActivityBind; if (bind != null) bind.SetRuntimeValue(referencedActivity, value); else MemberBind.SetValue(referencedActivity, this.Path, value); } // Dependency property /*else { Activity rootActivity = Helpers.GetRootActivity(activity); DependencyProperty dependencyProperty = DependencyProperty.FromName(this.Path, rootActivity.GetType()); if (dependencyProperty != null) { referencedActivity.SetValue(dependencyProperty, value); } }*/ } } private void OnRuntimeInitialized(Activity activity) { Activity dataSourceActivity = null; ActivityBind activityBind = ActivityBind.GetContextBind(this, activity, out dataSourceActivity); if (activityBind != null && dataSourceActivity != null) { Type companionType = dataSourceActivity.GetType(); if (companionType != null) { MemberInfo memberInfo = ActivityBind.GetMemberInfo(companionType, activityBind.Path, null); if (memberInfo != null) { if (memberInfo is FieldInfo || memberInfo is PropertyInfo || memberInfo is EventInfo) { if (activityBind.UserData[UserDataKeys.BindDataSource] == null) activityBind.UserData[UserDataKeys.BindDataSource] = new Hashtable(); ((Hashtable)activityBind.UserData[UserDataKeys.BindDataSource])[activity.QualifiedName] = memberInfo; if (dataSourceActivity != null) { if (activityBind.UserData[UserDataKeys.BindDataContextActivity] == null) activityBind.UserData[UserDataKeys.BindDataContextActivity] = new Hashtable(); ((Hashtable)activityBind.UserData[UserDataKeys.BindDataContextActivity])[activity.QualifiedName] = dataSourceActivity.QualifiedName; } } } /*else { Activity rootActivity = Helpers.GetRootActivity(activity); DependencyProperty dependencyProperty = DependencyProperty.FromName(activityBind.Path, rootActivity.GetType()); if (dependencyProperty != null) { if (activityBind.UserData[UserDataKeys.BindDataSource] == null) activityBind.UserData[UserDataKeys.BindDataSource] = new Hashtable(); ((Hashtable)activityBind.UserData[UserDataKeys.BindDataSource])[activity.QualifiedName] = dependencyProperty; if (dataSourceActivity != null) { if (activityBind.UserData[UserDataKeys.BindDataContextActivity] == null) activityBind.UserData[UserDataKeys.BindDataContextActivity] = new Hashtable(); ((Hashtable)activityBind.UserData[UserDataKeys.BindDataContextActivity])[activity.QualifiedName] = dataSourceActivity.QualifiedName; } } }*/ } } } public override string ToString() { Activity activity = UserData[UserDataKeys.BindDataContextActivity] as Activity; if (activity != null) { string bindString = String.Empty; if (!string.IsNullOrEmpty(Name)) bindString = Helpers.ParseActivityForBind(activity, Name).QualifiedName; if (!string.IsNullOrEmpty(Path)) { string path = Path; int indexOfSeparator = path.IndexOfAny(new char[] { '.', '/', '[' }); path = ((indexOfSeparator != -1)) ? path.Substring(0, indexOfSeparator) : path; bindString += (!String.IsNullOrEmpty(bindString)) ? "." + path : path; } return bindString; } else { return base.ToString(); } } #region Runtime / Validation Time Helpers internal static MemberInfo GetMemberInfo(Type dataSourceType, string path, Type targetType) { MemberInfo memberInfo = MemberBind.GetMemberInfo(dataSourceType, path); //The events can be either bound to properties or can be bound to methods, //There are cases where fields and methods can be of same name so in that case we either make sure for //in the case of event handlers we either find Property or a Method if (targetType != null && typeof(Delegate).IsAssignableFrom(targetType) && (memberInfo == null || !(memberInfo is EventInfo))) { MethodInfo delegateMethod = targetType.GetMethod("Invoke"); List paramTypes = new List(); foreach (ParameterInfo paramInfo in delegateMethod.GetParameters()) paramTypes.Add(paramInfo.ParameterType); memberInfo = dataSourceType.GetMethod(path, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, null, paramTypes.ToArray(), null); } return memberInfo; } private static object GetMemberValue(object dataSourceObject, MemberInfo memberInfo, string path, Type targetType) { object memberValue = null; if (memberInfo is FieldInfo || memberInfo is PropertyInfo || memberInfo is EventInfo) { memberValue = MemberBind.GetValue(memberInfo, dataSourceObject, path); /*if (memberValue != null && targetType != null && (memberValue.GetType().IsPrimitive || memberValue.GetType().IsEnum || memberValue.GetType() == typeof(string)) && !targetType.IsAssignableFrom(memberValue.GetType())) { try { memberValue = Convert.ChangeType(memberValue, targetType, CultureInfo.InvariantCulture); } catch (Exception e) { throw new Exception(SR.GetString(SR.Error_DataSourceTypeConversionFailed, memberInfo.Name, memberValue.ToString(), targetType.FullName), e); } }*/ } else if (targetType != null && memberInfo is MethodInfo) { memberValue = Delegate.CreateDelegate(targetType, dataSourceObject, (MethodInfo)memberInfo); //the wrapper method will never be static (even if the original one is) } else { throw new InvalidOperationException(SR.GetString(SR.Error_MemberNotFound)); } return memberValue; } //This function is used to get the outermost activity bind which is bound to actual field/property //The function is called in OnRuntimeInitialized and makes sure that we get outer target bind and cache //it so that at runtime we do not have to walk the bind chain to find the bound member private static ActivityBind GetContextBind(ActivityBind activityBind, Activity activity, out Activity contextActivity) { if (activityBind == null) throw new ArgumentNullException("activityBind"); if (activity == null) throw new ArgumentNullException("activity"); BindRecursionContext recursionContext = new BindRecursionContext(); ActivityBind contextBind = activityBind; contextActivity = activity; while (contextBind != null) { Activity resolvedActivity = Helpers.ParseActivityForBind(contextActivity, contextBind.Name); if (resolvedActivity == null) return null; object dataSourceObject = resolvedActivity; MemberInfo memberInfo = ActivityBind.GetMemberInfo(dataSourceObject.GetType(), contextBind.Path, null); if (memberInfo == null) { contextActivity = resolvedActivity; return contextBind; } else if (memberInfo is FieldInfo) { contextActivity = resolvedActivity; return contextBind; } else if (memberInfo is PropertyInfo && (memberInfo as PropertyInfo).PropertyType == typeof(ActivityBind) && dataSourceObject != null) { object value = MemberBind.GetValue(memberInfo, dataSourceObject, contextBind.Path); if (value is ActivityBind) { if (recursionContext.Contains(contextActivity, contextBind)) return null; recursionContext.Add(contextActivity, contextBind); contextActivity = resolvedActivity; contextBind = value as ActivityBind; } else { return null; } } else { return null; } } return contextBind; } #endregion #region DesignTime Integration (DO NOT CALL THESE AT RUNTIME) #region Helper Functions // This function replaces Activity1.code1 with /ParentContext.code1. This must be done for Binds that refer to // to fields, properties and methods in the top level custom activity data context. internal static string GetRelativePathExpression(Activity parentActivity, Activity childActivity) { string relativeBindExpression = String.Empty; Activity rootActivity = Helpers.GetRootActivity(childActivity); if (rootActivity == childActivity) relativeBindExpression = "/Self"; else relativeBindExpression = parentActivity.QualifiedName; return relativeBindExpression; } #region IPropertyValueProvider Implementation ICollection IPropertyValueProvider.GetPropertyValues(ITypeDescriptorContext context) { ArrayList values = new ArrayList(); if (string.Equals(context.PropertyDescriptor.Name, "Path", StringComparison.Ordinal) && !String.IsNullOrEmpty(Name) && context.PropertyDescriptor is ActivityBindPathPropertyDescriptor) { ITypeDescriptorContext outerPropertyContext = ((ActivityBindPathPropertyDescriptor)context.PropertyDescriptor).OuterPropertyContext; if (outerPropertyContext != null) { Activity activity = PropertyDescriptorUtils.GetComponent(outerPropertyContext) as Activity; if (activity != null) { Activity targetActivity = Helpers.ParseActivityForBind(activity, Name); if (targetActivity != null) { foreach (MemberInfo memberInfo in ActivityBindPropertyDescriptor.GetBindableMembers(targetActivity, outerPropertyContext)) values.Add(memberInfo.Name); } } } } return values; } #endregion #endregion #endregion } #endregion #region BindRecursionContext internal sealed class BindRecursionContext { private Hashtable activityBinds = new Hashtable(); public bool Contains(Activity activity, ActivityBind bind) { if (activity == null) throw new ArgumentNullException("activity"); if (bind == null) throw new ArgumentNullException("bind"); if (this.activityBinds[activity] != null) { List binds = this.activityBinds[activity] as List; foreach (ActivityBind prevBind in binds) { if (prevBind.Path == bind.Path) return true; } } return false; } public void Add(Activity activity, ActivityBind bind) { if (activity == null) throw new ArgumentNullException("activity"); if (bind == null) throw new ArgumentNullException("bind"); if (this.activityBinds[activity] == null) this.activityBinds[activity] = new List(); ((List)this.activityBinds[activity]).Add(bind); } } #endregion #region BindHelpers internal static class BindHelpers { internal static Type GetBaseType(IServiceProvider serviceProvider, PropertyValidationContext validationContext) { Type type = null; if (validationContext.Property is PropertyInfo) { type = Helpers.GetBaseType(validationContext.Property as PropertyInfo, validationContext.PropertyOwner, serviceProvider); } else if (validationContext.Property is DependencyProperty) { // DependencyProperty dependencyProperty = validationContext.Property as DependencyProperty; if (dependencyProperty != null) { if (type == null) { IDynamicPropertyTypeProvider basetypeProvider = validationContext.PropertyOwner as IDynamicPropertyTypeProvider; if (basetypeProvider != null) type = basetypeProvider.GetPropertyType(serviceProvider, dependencyProperty.Name); } if (type == null) type = dependencyProperty.PropertyType; } } return type; } internal static AccessTypes GetAccessType(IServiceProvider serviceProvider, PropertyValidationContext validationContext) { AccessTypes accessType = AccessTypes.Read; if (validationContext.Property is PropertyInfo) { accessType = Helpers.GetAccessType(validationContext.Property as PropertyInfo, validationContext.PropertyOwner, serviceProvider); } else if (validationContext.Property is DependencyProperty) { IDynamicPropertyTypeProvider basetypeProvider = validationContext.PropertyOwner as IDynamicPropertyTypeProvider; if (basetypeProvider != null) accessType = basetypeProvider.GetAccessType(serviceProvider, ((DependencyProperty)validationContext.Property).Name); } return accessType; } internal static object ResolveActivityPath(Activity refActivity, string path) { if (refActivity == null) throw new ArgumentNullException("refActivity"); if (path == null) throw new ArgumentNullException("path"); if (path.Length == 0) throw new ArgumentException(SR.GetString(SR.Error_EmptyPathValue), "path"); object value = refActivity; BindRecursionContext recursionContext = new BindRecursionContext(); PathWalker pathWalker = new PathWalker(); pathWalker.MemberFound += delegate(object sender, PathMemberInfoEventArgs eventArgs) { // If value is null, we don't want to use GetValue on the MemberInfo if (value == null) { eventArgs.Action = PathWalkAction.Cancel; //need to cancel the walk with the failure return result return; } switch (eventArgs.MemberKind) { case PathMemberKind.Field: try { value = (eventArgs.MemberInfo as FieldInfo).GetValue(value); } catch (Exception exception) { //in some cases the value might not be there yet (e.g. validation vs. runtime) value = null; eventArgs.Action = PathWalkAction.Cancel; //we should throw only if we are at the runtime if (!refActivity.DesignMode) { TargetInvocationException targetInvocationException = exception as TargetInvocationException; throw (targetInvocationException != null) ? targetInvocationException.InnerException : exception; } } break; case PathMemberKind.Event: EventInfo evt = eventArgs.MemberInfo as EventInfo; // GetValue() returns the actual value of the property. We need the Bind object here. // Find out if there is a matching dependency property and get the value throw the DP. DependencyProperty eventDependencyProperty = DependencyProperty.FromName(evt.Name, value.GetType()); if (eventDependencyProperty != null && value is DependencyObject) { if ((value as DependencyObject).IsBindingSet(eventDependencyProperty)) value = (value as DependencyObject).GetBinding(eventDependencyProperty); else value = (value as DependencyObject).GetHandler(eventDependencyProperty); } break; case PathMemberKind.Property: if (!(eventArgs.MemberInfo as PropertyInfo).CanRead) { eventArgs.Action = PathWalkAction.Cancel; return; } // GetValue() returns the actual value of the property. We need the Bind object here. // Find out if there is a matching dependency property and get the value throw the DP. DependencyProperty dependencyProperty = DependencyProperty.FromName(eventArgs.MemberInfo.Name, value.GetType()); if (dependencyProperty != null && value is DependencyObject && (value as DependencyObject).IsBindingSet(dependencyProperty)) value = (value as DependencyObject).GetBinding(dependencyProperty); else try { value = (eventArgs.MemberInfo as PropertyInfo).GetValue(value, null); } catch (Exception exception) { //property getter function might throw at design time, validation should not fail bacause of that value = null; eventArgs.Action = PathWalkAction.Cancel; //we should throw only if we are at the runtime if (!refActivity.DesignMode) { TargetInvocationException targetInvocationException = exception as TargetInvocationException; throw (targetInvocationException != null) ? targetInvocationException.InnerException : exception; } } break; case PathMemberKind.IndexedProperty: case PathMemberKind.Index: try { value = (eventArgs.MemberInfo as PropertyInfo).GetValue(value, BindingFlags.GetProperty, null, eventArgs.IndexParameters, CultureInfo.InvariantCulture); } catch (Exception exception) { //in some cases the value might not be there yet - e.g. array or dictionary is populated at runtime only (validation vs. runtime) value = null; eventArgs.Action = PathWalkAction.Cancel; //we should throw only if we are at the runtime if (!refActivity.DesignMode) { TargetInvocationException targetInvocationException = exception as TargetInvocationException; throw (targetInvocationException != null) ? targetInvocationException.InnerException : exception; } } break; } //need to unwrap the activity bind if we get one - to proceed with the actual field/property/delegate //do not unwrap if the property/field is itself of type ActivityBind //we should not unwrap the latest ActivityBind though - only intermediate ones //avoid circular reference problems with the BindRecursionContext if (value is ActivityBind && !eventArgs.LastMemberInThePath && GetMemberType(eventArgs.MemberInfo) != typeof(ActivityBind)) { while (value is ActivityBind) { ActivityBind activityBind = value as ActivityBind; if (recursionContext.Contains(refActivity, activityBind)) throw new InvalidOperationException(SR.GetString(SR.Bind_ActivityDataSourceRecursionDetected)); recursionContext.Add(refActivity, activityBind); value = activityBind.GetRuntimeValue(refActivity); } } }; if (pathWalker.TryWalkPropertyPath(refActivity.GetType(), path)) return value; else return null; } internal static PropertyInfo GetMatchedPropertyInfo(Type memberType, string[] aryArgName, object[] args) { if (memberType == null) throw new ArgumentNullException("memberType"); if (aryArgName == null) throw new ArgumentNullException("aryArgName"); if (args == null) throw new ArgumentNullException("args"); MemberInfo[][] aryMembers = new MemberInfo[][] { memberType.GetDefaultMembers(), null }; if (memberType.IsArray) { MemberInfo[] getMember = memberType.GetMember("Get"); //arrays will always implement that MemberInfo[] setMember = memberType.GetMember("Set"); //arrays will always implement that PropertyInfo getProperty = new ActivityBindPropertyInfo(memberType, getMember[0] as MethodInfo, setMember[0] as MethodInfo, string.Empty, null); aryMembers[1] = new MemberInfo[] { getProperty }; } for (int index = 0; index < aryMembers.Length; ++index) { if (aryMembers[index] == null) continue; MemberInfo[] defaultMembers = aryMembers[index]; foreach (MemberInfo memberInfo in defaultMembers) { PropertyInfo propertyInfo = memberInfo as PropertyInfo; if (propertyInfo != null) { if (MatchIndexerParameters(propertyInfo, aryArgName, args)) return propertyInfo; } } } return null; } internal static bool MatchIndexerParameters(PropertyInfo propertyInfo, string[] argNames, object[] args) { if (propertyInfo == null) throw new ArgumentNullException("propertyInfo"); if (argNames == null) throw new ArgumentNullException("argNames"); if (args == null) throw new ArgumentNullException("args"); ParameterInfo[] aryPI = propertyInfo.GetIndexParameters(); if (aryPI.Length != argNames.Length) return false; for (int index = 0; index < args.Length; ++index) { Type paramType = aryPI[index].ParameterType; if (paramType != typeof(String) && paramType != typeof(System.Int32)) return false; try { object arg = null; string argName = argNames[index].Trim(); if (paramType == typeof(String) && argName.StartsWith("\"", StringComparison.Ordinal) && argName.EndsWith("\"", StringComparison.Ordinal)) arg = argName.Substring(1, argName.Length - 2).Trim(); else if (paramType == typeof(System.Int32)) arg = Convert.ChangeType(argName, typeof(System.Int32), CultureInfo.InvariantCulture); if (arg != null) args.SetValue(arg, index); else return false; } catch { return false; } } return true; } internal static Type GetMemberType(MemberInfo memberInfo) { FieldInfo fieldInfo = memberInfo as FieldInfo; if (fieldInfo != null) return fieldInfo.FieldType; PropertyInfo propertyInfo = memberInfo as PropertyInfo; if (propertyInfo != null) { if (propertyInfo.PropertyType != null) return propertyInfo.PropertyType; //sometimes need to get the property type off the getter method MethodInfo getter = propertyInfo.GetGetMethod(); return getter.ReturnType; } EventInfo eventInfo = memberInfo as EventInfo; if (eventInfo != null) return eventInfo.EventHandlerType; return null; } } #endregion #region Class PathWalker internal enum PathMemberKind { Field, Event, Property, IndexedProperty, Index } internal enum PathWalkAction { Continue, Stop, Cancel }; //stop returns true, while cancel returns false internal class PathMemberInfoEventArgs : EventArgs { private string path; private Type parentType; private PathMemberKind memberKind; private MemberInfo memberInfo; private object[] indexParameters = new object[0]; //not empty for IndexedProperty and Index types private PathWalkAction action = PathWalkAction.Continue; private bool lastMemberInThePath = false; public PathMemberInfoEventArgs(string path, Type parentType, MemberInfo memberInfo, PathMemberKind memberKind, bool lastMemberInThePath) { if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path"); if (parentType == null) throw new ArgumentNullException("parentType"); if (memberInfo == null) throw new ArgumentNullException("memberInfo"); this.path = path; this.parentType = parentType; this.memberInfo = memberInfo; this.memberKind = memberKind; this.lastMemberInThePath = lastMemberInThePath; } public PathMemberInfoEventArgs(string path, Type parentType, MemberInfo memberInfo, PathMemberKind memberKind, bool lastMemberInThePath, object[] indexParameters) : this(path, parentType, memberInfo, memberKind, lastMemberInThePath) { this.indexParameters = indexParameters; } public string Path { get { return this.path; } } //public Type ParentType //{ // get { return this.parentType; } //} public MemberInfo MemberInfo { get { return this.memberInfo; } } public PathMemberKind MemberKind { get { return this.memberKind; } } public object[] IndexParameters { get { return this.indexParameters; } } public bool LastMemberInThePath { get { return this.lastMemberInThePath; } } public PathWalkAction Action { get { return this.action; } set { this.action = value; } } } internal class PathErrorInfoEventArgs : EventArgs { private SourceValueInfo info; private string currentPath; public PathErrorInfoEventArgs(SourceValueInfo info, string currentPath) { if (currentPath == null) throw new ArgumentNullException("currentPath"); this.info = info; this.currentPath = currentPath; } //public SourceValueInfo Info //{ // get { return this.info; } //} //public string CurrentPath //{ // get { return this.currentPath; } //} } //common path walker //it is based off the property types and the PathParser results //caller might keep a ref to the actual object and call Get/Set value on the members returned in the PathMemberInfoEventArgs internal class PathWalker { public EventHandler MemberFound; //on every member along the path public EventHandler PathErrorFound; //if there was an error parsing or walking the path private static MemberInfo[] PopulateMembers(Type type, string memberName) { List members = new List(); members.AddRange(type.GetMember(memberName, MemberTypes.Field | MemberTypes.Property | MemberTypes.Event | MemberTypes.Method, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)); if (type.IsInterface) { Type[] interfaces = type.GetInterfaces(); foreach (Type implementedInterface in interfaces) { members.AddRange(implementedInterface.GetMember(memberName, MemberTypes.Field | MemberTypes.Property | MemberTypes.Event | MemberTypes.Method, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy)); } } return members.ToArray(); } public bool TryWalkPropertyPath(Type rootType, string path) { if (rootType == null) throw new ArgumentNullException("rootType"); if (string.IsNullOrEmpty(path)) throw new ArgumentNullException("path"); Type propertyType = rootType; string currentPath = string.Empty; PathParser parser = new PathParser(); List pathInfo = parser.Parse(path, true); string parsingError = parser.Error; for (int i = 0; i < pathInfo.Count; i++) { SourceValueInfo info = pathInfo[i]; if (string.IsNullOrEmpty(info.name)) { if (PathErrorFound != null) PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath)); return false; } string additionalPath = (info.type == SourceValueType.Property) ? info.name : "[" + info.name + "]"; string newPath = (string.IsNullOrEmpty(currentPath)) ? additionalPath : currentPath + ((info.type == SourceValueType.Property) ? "." : string.Empty) + additionalPath; Type newPropertyType = null; MemberInfo newMemberInfo = null; switch (info.type) { case SourceValueType.Property: MemberInfo[] members = PopulateMembers(propertyType, info.name); if (members == null || members.Length == 0 || members[0] == null) { if (PathErrorFound != null) PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath)); return false; } newMemberInfo = members[0]; if (newMemberInfo is EventInfo || newMemberInfo is MethodInfo) { if (MemberFound != null) { PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, newMemberInfo, PathMemberKind.Event, i == pathInfo.Count - 1); MemberFound(this, args); if (args.Action == PathWalkAction.Cancel) return false; else if (args.Action == PathWalkAction.Stop) return true; } // return string.IsNullOrEmpty(parsingError); } else if (newMemberInfo is PropertyInfo) { //property getter could be an indexer PropertyInfo memberPropertyInfo = newMemberInfo as PropertyInfo; MethodInfo getterMethod = memberPropertyInfo.GetGetMethod(); MethodInfo setterMethod = memberPropertyInfo.GetSetMethod(); ActivityBindPropertyInfo properyInfo = new ActivityBindPropertyInfo(propertyType, getterMethod, setterMethod, memberPropertyInfo.Name, memberPropertyInfo); newPropertyType = properyInfo.PropertyType; ParameterInfo[] parameters = properyInfo.GetIndexParameters(); if (parameters.Length > 0) { //need to check that the next parsed element is an indexer if (i < pathInfo.Count - 1 && pathInfo[i + 1].type == SourceValueType.Indexer && !string.IsNullOrEmpty(pathInfo[i + 1].name)) { string[] arrayArgName = pathInfo[i + 1].name.Split(','); object[] arguments = new object[arrayArgName.Length]; //match the number/type of parameters from the following indexer item if (BindHelpers.MatchIndexerParameters(properyInfo, arrayArgName, arguments)) { newPath += "[" + pathInfo[i + 1].name + "]"; //indexer property, uses two of the parsed array elements if (MemberFound != null) { PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, properyInfo, PathMemberKind.IndexedProperty, i == pathInfo.Count - 2, arguments); MemberFound(this, args); if (args.Action == PathWalkAction.Cancel) return false; else if (args.Action == PathWalkAction.Stop) return true; } //skip the next indexer item too i++; } else { //parameters didn't match if (PathErrorFound != null) PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath)); return false; } } else { //trailing index is missing or empty for the indexed property if (PathErrorFound != null) PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath)); return false; } } else // parameters.Length == 0 { //a regular property if (MemberFound != null) { PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, properyInfo, PathMemberKind.Property, i == pathInfo.Count - 1); MemberFound(this, args); if (args.Action == PathWalkAction.Cancel) return false; else if (args.Action == PathWalkAction.Stop) return true; } } } else { //that would be a field if (MemberFound != null) { PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, newMemberInfo, PathMemberKind.Field, i == pathInfo.Count - 1); MemberFound(this, args); if (args.Action == PathWalkAction.Cancel) return false; else if (args.Action == PathWalkAction.Stop) return true; } newPropertyType = (newMemberInfo as FieldInfo).FieldType; } break; case SourceValueType.Indexer: if (!string.IsNullOrEmpty(info.name)) { string[] arrayArgName = info.name.Split(','); object[] arguments = new object[arrayArgName.Length]; PropertyInfo arrayPropertyInfo = BindHelpers.GetMatchedPropertyInfo(propertyType, arrayArgName, arguments); if (arrayPropertyInfo != null) { if (MemberFound != null) { PathMemberInfoEventArgs args = new PathMemberInfoEventArgs(newPath, propertyType, arrayPropertyInfo, PathMemberKind.Index, i == pathInfo.Count - 1, arguments); MemberFound(this, args); if (args.Action == PathWalkAction.Cancel) return false; else if (args.Action == PathWalkAction.Stop) return true; } newPropertyType = arrayPropertyInfo.PropertyType; if (newPropertyType == null) newPropertyType = arrayPropertyInfo.GetGetMethod().ReturnType; } else { //did not mach number/type of arguments if (PathErrorFound != null) PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath)); return false; } } else { //empty indexer if (PathErrorFound != null) PathErrorFound(this, new PathErrorInfoEventArgs(info, currentPath)); return false; } break; } propertyType = newPropertyType; currentPath = newPath; } return string.IsNullOrEmpty(parsingError); } } //multi-dimentional arrays dont implement default property //this fake property will help us to handle that internal class ActivityBindPropertyInfo : PropertyInfo { private MethodInfo getMethod; private MethodInfo setMethod; private Type declaringType; private string propertyName; private PropertyInfo originalPropertyInfo; //in vb fields get returned as properties public ActivityBindPropertyInfo(Type declaringType, MethodInfo getMethod, MethodInfo setMethod, string propertyName, PropertyInfo originalPropertyInfo) { if (declaringType == null) throw new ArgumentNullException("declaringType"); if (propertyName == null) throw new ArgumentNullException("propertyName"); this.declaringType = declaringType; this.getMethod = getMethod; this.setMethod = setMethod; this.propertyName = propertyName; this.originalPropertyInfo = originalPropertyInfo; //could be null for array indexers. } public override string Name { get { return this.propertyName; } } public override MethodInfo GetGetMethod(bool nonPublic) { return this.getMethod; } public override MethodInfo GetSetMethod(bool nonPublic) { return this.setMethod; } public override Type PropertyType { get { if (this.getMethod != null) return this.getMethod.ReturnType; else if (this.originalPropertyInfo != null) return this.originalPropertyInfo.PropertyType; else return typeof(object); } } public override ParameterInfo[] GetIndexParameters() { if (this.getMethod != null) return this.getMethod.GetParameters(); else if (this.originalPropertyInfo != null) return this.originalPropertyInfo.GetIndexParameters(); else return new ParameterInfo[0]; } public override object GetValue(object obj, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) { if (this.getMethod == null && (this.originalPropertyInfo == null || !this.originalPropertyInfo.CanRead)) throw new InvalidOperationException(SR.GetString(SR.Error_PropertyHasNoGetterDefined, this.propertyName)); if (this.getMethod != null) return this.getMethod.Invoke(obj, invokeAttr, binder, index, culture); else return this.originalPropertyInfo.GetValue(obj, invokeAttr, binder, index, culture); } public override void SetValue(object obj, object value, BindingFlags invokeAttr, Binder binder, object[] index, CultureInfo culture) { if (this.setMethod == null && (this.originalPropertyInfo == null || !this.originalPropertyInfo.CanWrite)) throw new InvalidOperationException(SR.GetString(SR.Error_PropertyHasNoSetterDefined, this.propertyName)); if (this.setMethod != null) { object[] parameters = new object[((index != null) ? index.Length : 0) + 1]; parameters[((index != null) ? index.Length : 0)] = value; if (index != null) index.CopyTo(parameters, 0); this.setMethod.Invoke(obj, invokeAttr, binder, parameters, culture); } else { this.originalPropertyInfo.SetValue(obj, value, invokeAttr, binder, index, culture); } } public override MethodInfo[] GetAccessors(bool nonPublic) { return new MethodInfo[] { this.getMethod, this.setMethod }; } public override PropertyAttributes Attributes { get { return PropertyAttributes.None; } } public override bool CanRead { get { if (this.getMethod != null) return true; else if (this.originalPropertyInfo != null) return this.originalPropertyInfo.CanRead; else return false; } } public override bool CanWrite { get { if (this.setMethod != null) return true; else if (this.originalPropertyInfo != null) return this.originalPropertyInfo.CanWrite; else return false; } } public override Type DeclaringType { get { return this.declaringType; } } public override Type ReflectedType { get { return this.declaringType; } } public override object[] GetCustomAttributes(bool inherit) { return new object[0]; } public override object[] GetCustomAttributes(Type attributeType, bool inherit) { return new object[0]; } public override bool IsDefined(Type attributeType, bool inherit) { throw new NotSupportedException(); } } #endregion #region PathParser internal enum SourceValueType { Property, Indexer }; internal enum DrillIn { Never, IfNeeded }; internal struct SourceValueInfo { internal SourceValueType type; internal DrillIn drillIn; internal string name; internal SourceValueInfo(SourceValueType t, DrillIn d, string n) { type = t; drillIn = d; name = n; } } internal sealed class PathParser { private string error = string.Empty; private State state; private string pathValue; private int index; private int pathLength; private DrillIn drillIn; private List al = new List(); private const char NullChar = Char.MinValue; private static List EmptyInfo = new List(1); private static string SpecialChars = @".[]"; private enum State { Init, Prop, Done }; internal String Error { get { return error; } } internal List Parse(string path, bool returnResultBeforeError) { this.pathValue = (path != null) ? path.Trim() : String.Empty; this.pathLength = this.pathValue.Length; this.index = 0; this.drillIn = DrillIn.IfNeeded; this.al.Clear(); this.error = null; this.state = State.Init; if (this.pathLength > 0 && this.pathValue[0] == '.') { //empty first prop - > input path was like ".bar"; need to add first empty property SourceValueInfo info = new SourceValueInfo(SourceValueType.Property, this.drillIn, string.Empty); this.al.Add(info); } while (this.state != State.Done) { char c = (this.index < this.pathLength) ? this.pathValue[this.index] : NullChar; switch (this.state) { case State.Init: switch (c) { case '/': case '.': case '[': case NullChar: this.state = State.Prop; break; case ']'://unexpected close indexer, report error this.error = "path[" + this.index + "] = " + c; return returnResultBeforeError ? this.al : EmptyInfo; default: AddProperty(); break; } break; case State.Prop: bool isIndexer = false; switch (c) { case '.': this.drillIn = DrillIn.Never; break; case '[': isIndexer = true; break; case NullChar: --this.index; break; default: this.error = "path[" + this.index + "] = " + c; return returnResultBeforeError ? this.al : EmptyInfo; } ++this.index; // skip over special character if (isIndexer) AddIndexer(); else AddProperty(); break; } } return (this.error == null || returnResultBeforeError) ? this.al : EmptyInfo; } private void AddProperty() { int start = this.index; while (this.index < this.pathLength && SpecialChars.IndexOf(this.pathValue[this.index]) < 0) ++this.index; string name = this.pathValue.Substring(start, this.index - start).Trim(); SourceValueInfo info = new SourceValueInfo( SourceValueType.Property, this.drillIn, name); this.al.Add(info); StartNewLevel(); } private void AddIndexer() { int start = this.index; int level = 1; while (level > 0) { if (this.index >= this.pathLength) { return; } if (this.pathValue[this.index] == '[') ++level; else if (this.pathValue[this.index] == ']') --level; ++this.index; } string name = this.pathValue.Substring(start, this.index - start - 1).Trim(); SourceValueInfo info = new SourceValueInfo( SourceValueType.Indexer, this.drillIn, name); this.al.Add(info); StartNewLevel(); } private void StartNewLevel() { if (this.index >= this.pathLength) this.state = State.Done; this.drillIn = DrillIn.Never; } } #endregion }