public AttributeCloner( TAttribute source, BindingDataContract bindingDataContract, INameResolver nameResolver = null, Func <TAttribute, Task <TAttribute> > hook = null) { _hook = hook; nameResolver = nameResolver ?? new EmptyNameResolver(); _source = source; Type attributeType = typeof(TAttribute); PropertyInfo[] allProperties = attributeType.GetProperties(Flags); // Create dictionary of all non-null properties on source attribute. Dictionary <string, PropertyInfo> nonNullProps = allProperties .Where(prop => prop.GetValue(source) != null) .ToDictionary(prop => prop.Name, prop => prop, StringComparer.OrdinalIgnoreCase); // Pick the ctor with the longest parameter list where all are matched to non-null props. var ctorAndParams = attributeType.GetConstructors(Flags) .Select(ctor => new { ctor = ctor, parameters = ctor.GetParameters() }) .OrderByDescending(tuple => tuple.parameters.Length) .FirstOrDefault(tuple => tuple.parameters.All(param => nonNullProps.ContainsKey(param.Name))); if (ctorAndParams == null) { throw new InvalidOperationException("Can't figure out which ctor to call."); } _matchedCtor = ctorAndParams.ctor; // Get appropriate binding data resolver (appsetting, autoresolve, or originalValue) for each constructor parameter _ctorParamResolvers = ctorAndParams.parameters .Select(param => GetResolver(nonNullProps[param.Name], nameResolver, bindingDataContract)) .ToArray(); // Get appropriate binding data resolver (appsetting, autoresolve, or originalValue) for each writeable property _propertySetters = allProperties .Where(prop => prop.CanWrite) .Select(prop => { var resolver = GetResolver(prop, nameResolver, bindingDataContract); return((Action <TAttribute, BindingData>)((attr, data) => prop.SetValue(attr, resolver(attr, data)))); }) .ToArray(); }
// transforms binding data to appropriate resolver (appsetting, autoresolve, or originalValue) private BindingDataResolver GetResolver(PropertyInfo propInfo, INameResolver nameResolver, BindingDataContract contract) { object originalValue = propInfo.GetValue(_source); AppSettingAttribute appSettingAttr = propInfo.GetCustomAttribute <AppSettingAttribute>(); AutoResolveAttribute autoResolveAttr = propInfo.GetCustomAttribute <AutoResolveAttribute>(); if (appSettingAttr != null && autoResolveAttr != null) { throw new InvalidOperationException($"Property '{propInfo.Name}' cannot be annotated with both AppSetting and AutoResolve."); } // first try to resolve with app setting if (appSettingAttr != null) { return(GetAppSettingResolver((string)originalValue, appSettingAttr, nameResolver, propInfo)); } // try to resolve with auto resolve ({...}, %...%) if (autoResolveAttr != null && originalValue != null) { _autoResolves[propInfo] = autoResolveAttr; return(GetTemplateResolver((string)originalValue, autoResolveAttr, nameResolver, propInfo, contract)); } // resolve the original value return((newAttr, bindingData) => originalValue); }
// AutoResolve internal static BindingDataResolver GetTemplateResolver(string originalValue, AutoResolveAttribute attr, INameResolver nameResolver, PropertyInfo propInfo, BindingDataContract contract) { string resolvedValue = nameResolver.ResolveWholeString(originalValue); var template = BindingTemplate.FromString(resolvedValue); IResolutionPolicy policy = GetPolicy(attr.ResolutionPolicyType, propInfo); template.ValidateContractCompatibility(contract); return((newAttr, bindingData) => TemplateBind(policy, propInfo, newAttr, template, bindingData)); }