File: System\Data\Common\DbConnectionStringBuilder.cs
Web Access
Project: src\src\libraries\System.Data.Common\src\System.Data.Common.csproj (System.Data.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Text;
 
namespace System.Data.Common
{
    [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2113:ReflectionToRequiresUnreferencedCode",
        Justification = "The use of GetType preserves ICustomTypeDescriptor members with RequiresUnreferencedCode, but the GetType callsites either "
            + "occur in RequiresUnreferencedCode scopes, or have individually justified suppressions.")]
    [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
        Justification = "The use of GetType preserves implementation of ICustomTypeDescriptor members with RequiresUnreferencedCode, but the GetType callsites either "
            + "occur in RequiresUnreferencedCode scopes, or have individually justified suppressions.")]
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
    public class DbConnectionStringBuilder : IDictionary, ICustomTypeDescriptor
    {
        // keyword->value currently listed in the connection string
        private Dictionary<string, object>? _currentValues;
 
        // cached connectionstring to avoid constant rebuilding
        // and to return a user's connectionstring as is until editing occurs
        private string? _connectionString = string.Empty;
 
        private PropertyDescriptorCollection? _propertyDescriptors;
        private bool _browsableConnectionString = true;
        private readonly bool _useOdbcRules;
 
        private static int s_objectTypeCount; // Bid counter
        internal readonly int _objectID = System.Threading.Interlocked.Increment(ref s_objectTypeCount);
 
        public DbConnectionStringBuilder()
        {
        }
 
        public DbConnectionStringBuilder(bool useOdbcRules)
        {
            _useOdbcRules = useOdbcRules;
        }
 
        private ICollection Collection
        {
            get { return CurrentValues; }
        }
        private IDictionary Dictionary
        {
            get { return CurrentValues; }
        }
        private Dictionary<string, object> CurrentValues
        {
            get
            {
                Dictionary<string, object>? values = _currentValues;
                if (null == values)
                {
                    values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
                    _currentValues = values;
                }
                return values;
            }
        }
 
        object? IDictionary.this[object keyword]
        {
            // delegate to this[string keyword]
            get { return this[ObjectToString(keyword)]; }
            set { this[ObjectToString(keyword)] = value!; }
        }
 
        [Browsable(false)]
        [AllowNull]
        public virtual object this[string keyword]
        {
            get
            {
                DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.get_Item|API> {0}, keyword='{1}'", ObjectID, keyword);
                ADP.CheckArgumentNull(keyword, nameof(keyword));
                object? value;
                if (CurrentValues.TryGetValue(keyword, out value))
                {
                    return value;
                }
                throw ADP.KeywordNotSupported(keyword);
            }
            set
            {
                ADP.CheckArgumentNull(keyword, nameof(keyword));
                bool flag;
                if (null != value)
                {
                    string keyvalue = DbConnectionStringBuilderUtil.ConvertToString(value);
                    DbConnectionOptions.ValidateKeyValuePair(keyword, keyvalue);
 
                    flag = CurrentValues.ContainsKey(keyword);
 
                    // store keyword/value pair
                    CurrentValues[keyword] = keyvalue;
                }
                else
                {
                    flag = Remove(keyword);
                }
                _connectionString = null;
                if (flag)
                {
                    _propertyDescriptors = null;
                }
            }
        }
 
        [Browsable(false)]
        [DesignOnly(true)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [EditorBrowsable(EditorBrowsableState.Never)]
        public bool BrowsableConnectionString
        {
            get
            {
                return _browsableConnectionString;
            }
            set
            {
                _browsableConnectionString = value;
                _propertyDescriptors = null;
            }
        }
 
        [RefreshPropertiesAttribute(RefreshProperties.All)]
        [AllowNull]
        public string ConnectionString
        {
            get
            {
                DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.get_ConnectionString|API> {0}", ObjectID);
                string? connectionString = _connectionString;
                if (null == connectionString)
                {
                    StringBuilder builder = new StringBuilder();
                    foreach (string keyword in Keys)
                    {
                        Debug.Assert(keyword != null);
                        object? value;
                        if (ShouldSerialize(keyword) && TryGetValue(keyword, out value))
                        {
                            string? keyvalue = ConvertValueToString(value);
                            AppendKeyValuePair(builder, keyword, keyvalue, _useOdbcRules);
                        }
                    }
                    connectionString = builder.ToString();
                    _connectionString = connectionString;
                }
                return connectionString;
            }
            set
            {
                DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.set_ConnectionString|API> {0}", ObjectID);
                DbConnectionOptions constr = new DbConnectionOptions(value, null, _useOdbcRules);
                string originalValue = ConnectionString;
                Clear();
                try
                {
                    for (NameValuePair? pair = constr._keyChain; null != pair; pair = pair.Next)
                    {
                        if (null != pair.Value)
                        {
                            this[pair.Name] = pair.Value;
                        }
                        else
                        {
                            Remove(pair.Name);
                        }
                    }
                    _connectionString = null;
                }
                catch (ArgumentException)
                {
                    // restore original string
#pragma warning disable CA2011 // avoid infinite recursion, but this isn't that; it's restoring the original string
                    ConnectionString = originalValue;
#pragma warning restore CA2011
                    _connectionString = originalValue;
                    throw;
                }
            }
        }
 
        [Browsable(false)]
        public virtual int Count
        {
            get { return CurrentValues.Count; }
        }
 
        [Browsable(false)]
        public bool IsReadOnly
        {
            get { return false; }
        }
 
        [Browsable(false)]
        public virtual bool IsFixedSize
        {
            get { return false; }
        }
        bool ICollection.IsSynchronized
        {
            get { return Collection.IsSynchronized; }
        }
 
        [Browsable(false)]
        public virtual ICollection Keys
        {
            get
            {
                DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.Keys|API> {0}", ObjectID);
                return Dictionary.Keys;
            }
        }
 
        internal int ObjectID
        {
            get
            {
                return _objectID;
            }
        }
 
        object ICollection.SyncRoot
        {
            get { return Collection.SyncRoot; }
        }
 
        [Browsable(false)]
        public virtual ICollection Values
        {
            get
            {
                DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.Values|API> {0}", ObjectID);
                ICollection<string> keys = (ICollection<string>)Keys;
                IEnumerator<string> keylist = keys.GetEnumerator();
                object[] values = new object[keys.Count];
                for (int i = 0; i < values.Length; ++i)
                {
                    keylist.MoveNext();
                    values[i] = this[keylist.Current];
                    Debug.Assert(null != values[i], $"null value {keylist.Current}");
                }
                return new ReadOnlyCollection<object>(values);
            }
        }
 
        internal virtual string? ConvertValueToString(object? value)
        {
            return (value == null) ? null : Convert.ToString(value, CultureInfo.InvariantCulture);
        }
 
        void IDictionary.Add(object keyword, object? value)
        {
            Add(ObjectToString(keyword), value!);
        }
        public void Add(string keyword, object value)
        {
            this[keyword] = value;
        }
 
        public static void AppendKeyValuePair(StringBuilder builder, string keyword, string? value)
        {
            DbConnectionOptions.AppendKeyValuePairBuilder(builder, keyword, value, false);
        }
 
        public static void AppendKeyValuePair(StringBuilder builder, string keyword, string? value, bool useOdbcRules)
        {
            DbConnectionOptions.AppendKeyValuePairBuilder(builder, keyword, value, useOdbcRules);
        }
 
        public virtual void Clear()
        {
            DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.Clear|API>");
            _connectionString = string.Empty;
            _propertyDescriptors = null;
            CurrentValues.Clear();
        }
 
        protected internal void ClearPropertyDescriptors()
        {
            _propertyDescriptors = null;
        }
 
        // does the keyword exist as a strongly typed keyword or as a stored value
        bool IDictionary.Contains(object keyword)
        {
            return ContainsKey(ObjectToString(keyword));
        }
        public virtual bool ContainsKey(string keyword)
        {
            ADP.CheckArgumentNull(keyword, nameof(keyword));
            return CurrentValues.ContainsKey(keyword);
        }
 
        void ICollection.CopyTo(Array array, int index)
        {
            DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.ICollection.CopyTo|API> {0}", ObjectID);
            Collection.CopyTo(array, index);
        }
 
        public virtual bool EquivalentTo(DbConnectionStringBuilder connectionStringBuilder)
        {
            ADP.CheckArgumentNull(connectionStringBuilder, nameof(connectionStringBuilder));
 
            DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.EquivalentTo|API> {0}, connectionStringBuilder={1}", ObjectID, connectionStringBuilder.ObjectID);
            if ((GetType() != connectionStringBuilder.GetType()) || (CurrentValues.Count != connectionStringBuilder.CurrentValues.Count))
            {
                return false;
            }
            object? value;
            foreach (KeyValuePair<string, object> entry in CurrentValues)
            {
                if (!connectionStringBuilder.CurrentValues.TryGetValue(entry.Key, out value) || !entry.Value.Equals(value))
                {
                    return false;
                }
            }
            return true;
        }
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.IEnumerable.GetEnumerator|API> {0}", ObjectID);
            return Collection.GetEnumerator();
        }
        IDictionaryEnumerator IDictionary.GetEnumerator()
        {
            DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.IDictionary.GetEnumerator|API> {0}", ObjectID);
            return Dictionary.GetEnumerator();
        }
 
        private static string ObjectToString(object keyword)
        {
            try
            {
                return (string)keyword;
            }
            catch (InvalidCastException)
            {
                // BUGBUG: the message is not localized.
                throw new ArgumentException("not a string", nameof(keyword));
            }
        }
 
        void IDictionary.Remove(object keyword)
        {
            Remove(ObjectToString(keyword));
        }
        public virtual bool Remove(string keyword)
        {
            DataCommonEventSource.Log.Trace("<comm.DbConnectionStringBuilder.Remove|API> {0}, keyword='{1}'", ObjectID, keyword);
            ADP.CheckArgumentNull(keyword, nameof(keyword));
            if (CurrentValues.Remove(keyword))
            {
                _connectionString = null;
                _propertyDescriptors = null;
                return true;
            }
            return false;
        }
 
        // does the keyword exist as a stored value or something that should always be persisted
        public virtual bool ShouldSerialize(string keyword)
        {
            ADP.CheckArgumentNull(keyword, nameof(keyword));
            return CurrentValues.ContainsKey(keyword);
        }
 
        public override string ToString()
        {
            return ConnectionString;
        }
 
        public virtual bool TryGetValue(string keyword, [NotNullWhen(true)] out object? value)
        {
            ADP.CheckArgumentNull(keyword, nameof(keyword));
            return CurrentValues.TryGetValue(keyword, out value);
        }
 
        internal static Attribute[] GetAttributesFromCollection(AttributeCollection collection)
        {
            Attribute[] attributes = new Attribute[collection.Count];
            collection.CopyTo(attributes, 0);
            return attributes;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
            Justification = "The use of GetType preserves this member with RequiresUnreferencedCode, but the GetType callsites either "
                + "occur in RequiresUnreferencedCode scopes, or have individually justified suppressions.")]
        [RequiresUnreferencedCode("PropertyDescriptor's PropertyType cannot be statically discovered.")]
        private PropertyDescriptorCollection GetProperties()
        {
            PropertyDescriptorCollection? propertyDescriptors = _propertyDescriptors;
            if (null == propertyDescriptors)
            {
                long logScopeId = DataCommonEventSource.Log.EnterScope("<comm.DbConnectionStringBuilder.GetProperties|INFO> {0}", ObjectID);
                try
                {
                    Hashtable descriptors = new Hashtable(StringComparer.OrdinalIgnoreCase);
 
                    GetProperties(descriptors);
 
                    PropertyDescriptor[] properties = new PropertyDescriptor[descriptors.Count];
                    descriptors.Values.CopyTo(properties, 0);
                    propertyDescriptors = new PropertyDescriptorCollection(properties);
                    _propertyDescriptors = propertyDescriptors;
                }
                finally
                {
                    DataCommonEventSource.Log.ExitScope(logScopeId);
                }
            }
            return propertyDescriptors;
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
            Justification = "The use of GetType preserves this member with RequiresUnreferencedCode, but the GetType callsites either "
                + "occur in RequiresUnreferencedCode scopes, or have individually justified suppressions.")]
        [RequiresUnreferencedCode("PropertyDescriptor's PropertyType cannot be statically discovered.")]
        protected virtual void GetProperties(Hashtable propertyDescriptors)
        {
            long logScopeId = DataCommonEventSource.Log.EnterScope("<comm.DbConnectionStringBuilder.GetProperties|API> {0}", ObjectID);
            try
            {
                // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
                // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
                GetType();
 
                // show all strongly typed properties (not already added)
                // except ConnectionString iff BrowsableConnectionString
                Attribute[]? attributes;
                foreach (PropertyDescriptor reflected in TypeDescriptor.GetProperties(this, true))
                {
                    Debug.Assert(reflected != null);
                    if (ADP.ConnectionString != reflected.Name)
                    {
                        string displayName = reflected.DisplayName;
                        if (!propertyDescriptors.ContainsKey(displayName))
                        {
                            attributes = GetAttributesFromCollection(reflected.Attributes);
                            PropertyDescriptor descriptor = new DbConnectionStringBuilderDescriptor(reflected.Name,
                                    reflected.ComponentType, reflected.PropertyType, reflected.IsReadOnly, attributes);
                            propertyDescriptors[displayName] = descriptor;
                        }
                        // else added by derived class first
                    }
                    else if (BrowsableConnectionString)
                    {
                        propertyDescriptors[ADP.ConnectionString] = reflected;
                    }
                    else
                    {
                        propertyDescriptors.Remove(ADP.ConnectionString);
                    }
                }
 
                // all keywords in Keys list that do not have strongly typed property, ODBC case
                if (!IsFixedSize)
                {
                    attributes = null;
                    foreach (string keyword in Keys)
                    {
                        Debug.Assert(keyword != null);
                        if (!propertyDescriptors.ContainsKey(keyword))
                        {
                            object value = this[keyword];
 
                            Type vtype;
                            if (null != value)
                            {
                                vtype = value.GetType();
                                if (typeof(string) == vtype)
                                {
                                    int tmp1;
                                    if (int.TryParse((string)value, out tmp1))
                                    {
                                        vtype = typeof(int);
                                    }
                                    else
                                    {
                                        bool tmp2;
                                        if (bool.TryParse((string)value, out tmp2))
                                        {
                                            vtype = typeof(bool);
                                        }
                                    }
                                }
                            }
                            else
                            {
                                vtype = typeof(string);
                            }
 
                            Attribute[]? useAttributes = null;
                            if (StringComparer.OrdinalIgnoreCase.Equals(DbConnectionStringKeywords.Password, keyword) ||
                                StringComparer.OrdinalIgnoreCase.Equals(DbConnectionStringSynonyms.Pwd, keyword))
                            {
                                useAttributes = new Attribute[] {
                                    BrowsableAttribute.Yes,
                                    PasswordPropertyTextAttribute.Yes,
                                    RefreshPropertiesAttribute.All,
                                };
                            }
                            else if (null == attributes)
                            {
                                attributes = new Attribute[] {
                                    BrowsableAttribute.Yes,
                                    RefreshPropertiesAttribute.All,
                                };
                                useAttributes = attributes;
                            }
 
                            PropertyDescriptor descriptor = new DbConnectionStringBuilderDescriptor(keyword,
                                                                    GetType(), vtype, false, useAttributes!);
                            propertyDescriptors[keyword] = descriptor;
                        }
                    }
                }
            }
            finally
            {
                DataCommonEventSource.Log.ExitScope(logScopeId);
            }
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2112:ReflectionToRequiresUnreferencedCode",
            Justification = "The use of GetType preserves this member with RequiresUnreferencedCode, but the GetType callsites either "
                + "occur in RequiresUnreferencedCode scopes, or have individually justified suppressions.")]
        [RequiresUnreferencedCode("The public parameterless constructor or the 'Default' static field may be trimmed from the Attribute's Type.")]
        private PropertyDescriptorCollection GetProperties(Attribute[]? attributes)
        {
            PropertyDescriptorCollection propertyDescriptors = GetProperties();
            if ((null == attributes) || (0 == attributes.Length))
            {
                // Basic case has no filtering
                return propertyDescriptors;
            }
 
            // Create an array that is guaranteed to hold all attributes
            PropertyDescriptor[] propertiesArray = new PropertyDescriptor[propertyDescriptors.Count];
 
            // Create an index to reference into this array
            int index = 0;
 
            // Iterate over each property
            foreach (PropertyDescriptor property in propertyDescriptors)
            {
                Debug.Assert(property != null);
 
                // Identify if this property's attributes match the specification
                bool match = true;
                foreach (Attribute attribute in attributes)
                {
                    Attribute? attr = property.Attributes[attribute.GetType()];
                    if ((attr == null && !attribute.IsDefaultAttribute()) || attr?.Match(attribute) == false)
                    {
                        match = false;
                        break;
                    }
                }
 
                // If this property matches, add it to the array
                if (match)
                {
                    propertiesArray[index] = property;
                    index++;
                }
            }
 
            // Create a new array that only contains the filtered properties
            PropertyDescriptor[] filteredPropertiesArray = new PropertyDescriptor[index];
            Array.Copy(propertiesArray, filteredPropertiesArray, index);
 
            return new PropertyDescriptorCollection(filteredPropertiesArray);
        }
 
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The component type's class name is preserved because this class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
        string? ICustomTypeDescriptor.GetClassName()
        {
            // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
            // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
            GetType();
            return TypeDescriptor.GetClassName(this, true);
        }
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The component type's component name is preserved because this class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
        string? ICustomTypeDescriptor.GetComponentName()
        {
            // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
            // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
            GetType();
            return TypeDescriptor.GetComponentName(this, true);
        }
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The component type's attributes are preserved because this class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return TypeDescriptor.GetAttributes(this, true);
        }
        [RequiresUnreferencedCode("Design-time attributes are not preserved when trimming. Types referenced by attributes like EditorAttribute and DesignerAttribute may not be available after trimming.")]
        object? ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(this, editorBaseType, true);
        }
        [RequiresUnreferencedCode("Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.")]
        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return TypeDescriptor.GetConverter(this, true);
        }
        [RequiresUnreferencedCode("PropertyDescriptor's PropertyType cannot be statically discovered.")]
        PropertyDescriptor? ICustomTypeDescriptor.GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(this, true);
        }
        [RequiresUnreferencedCode("PropertyDescriptor's PropertyType cannot be statically discovered.")]
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return GetProperties();
        }
        [RequiresUnreferencedCode("PropertyDescriptor's PropertyType cannot be statically discovered. The public parameterless constructor or the 'Default' static field may be trimmed from the Attribute's Type.")]
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes)
        {
            return GetProperties(attributes);
        }
        [RequiresUnreferencedCode("The built-in EventDescriptor implementation uses Reflection which requires unreferenced code.")]
        EventDescriptor? ICustomTypeDescriptor.GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(this, true);
        }
        [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode",
            Justification = "The component type's events are preserved because this class is marked with [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]")]
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            // Below call is necessary to tell the trimmer that it should mark derived types appropriately.
            // We cannot use overload which takes type because the result might differ if derived class implements ICustomTypeDescriptor.
            GetType();
            return TypeDescriptor.GetEvents(this, true);
        }
        [RequiresUnreferencedCode("The public parameterless constructor or the 'Default' static field may be trimmed from the Attribute's Type.")]
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[]? attributes)
        {
            return TypeDescriptor.GetEvents(this, attributes, true);
        }
        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor? pd)
        {
            return this;
        }
    }
}