1
+ using System ;
2
+ using System . Collections . Generic ;
3
+ using System . Data . Common ;
4
+ using System . Globalization ;
5
+ using System . Linq ;
6
+ using System . Reflection ;
7
+ using System . Text ;
8
+ using System . Threading ;
9
+
10
+ using CodeJam . Collections ;
11
+ using CodeJam . Reflection ;
12
+ using CodeJam . Strings ;
13
+ using CodeJam . Targeting ;
14
+
15
+ using JetBrains . Annotations ;
16
+
17
+ namespace CodeJam . ConnectionStrings
18
+ {
19
+ partial class ConnectionStringBase
20
+ {
21
+ private class StringBuilderWrapper : DbConnectionStringBuilder
22
+ {
23
+ private const string _nonBrowsableValue = "..." ;
24
+
25
+ private static IReadOnlyDictionary < string , KeywordDescriptor > GetDescriptorsCore ( Type type )
26
+ {
27
+ KeywordDescriptor GetDescriptor ( PropertyInfo property ) =>
28
+ new KeywordDescriptor (
29
+ property . Name ,
30
+ property . PropertyType ,
31
+ #if NET35_OR_GREATER || TARGETS_NETSTANDARD || TARGETS_NETCOREAPP
32
+ property . IsRequired ( ) ,
33
+ #else
34
+ false ,
35
+ #endif
36
+ property. IsBrowsable ( ) ) ;
37
+
38
+ // Explicit ordering from most derived to base. Explanation:
39
+ // The GetProperties method does not return properties in a particular order, such as alphabetical or declaration order.
40
+ // Your code must not depend on the order in which properties are returned, because that order varies.
41
+ var typeChain = Sequence . CreateWhileNotNull (
42
+ type ,
43
+ t => t . GetBaseType ( ) is var baseType && baseType != typeof ( ConnectionStringBase )
44
+ ? baseType
45
+ : null ) ;
46
+ var properties = typeChain
47
+ . SelectMany ( t => t . GetProperties ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . DeclaredOnly ) ) ;
48
+ #if NET45_OR_GREATER || TARGETS_NETSTANDARD || TARGETS_NETCOREAPP
49
+ var result = new Dictionary < string , KeywordDescriptor > ( StringComparer . OrdinalIgnoreCase ) ;
50
+ #else
51
+ var result = new DictionaryEx < string , KeywordDescriptor > ( StringComparer . OrdinalIgnoreCase ) ;
52
+ #endif
53
+ foreach ( var prop in properties )
54
+ {
55
+ // DONTTOUCH: most derived property wins
56
+ if ( result . ContainsKey ( prop . Name ) )
57
+ continue ;
58
+
59
+ result [ prop . Name ] = GetDescriptor ( prop ) ;
60
+ }
61
+ return result ;
62
+ }
63
+
64
+ private static readonly Func < Type , IReadOnlyDictionary < string , KeywordDescriptor > > _keywordsCache = Algorithms
65
+ . Memoize (
66
+ ( Type type ) => GetDescriptorsCore ( type ) ,
67
+ LazyThreadSafetyMode . ExecutionAndPublication ) ;
68
+
69
+ private readonly Type _descriptorType ;
70
+
71
+ public StringBuilderWrapper ( string connectionString , Type descriptorType )
72
+ {
73
+ _descriptorType = descriptorType ;
74
+ if ( connectionString != null )
75
+ ConnectionString = connectionString ;
76
+ }
77
+
78
+ [ NotNull ]
79
+ public IReadOnlyDictionary < string , KeywordDescriptor > Keywords => _keywordsCache ( _descriptorType ) ;
80
+
81
+ [ NotNull ]
82
+ public new string ConnectionString
83
+ {
84
+ get => base . ConnectionString ;
85
+ set
86
+ {
87
+ base . ConnectionString = value ;
88
+ if ( value . NotNullNorEmpty ( ) )
89
+ {
90
+ foreach ( var nameRequiredPair in Keywords . Where ( p => p . Value . IsRequired ) )
91
+ {
92
+ if ( ! ContainsKey ( nameRequiredPair . Key ) )
93
+ throw CodeExceptions . Argument (
94
+ nameof ( ConnectionString ) ,
95
+ $ "The value of required { nameRequiredPair . Key } connection string parameter is empty.") ;
96
+ }
97
+ }
98
+ }
99
+ }
100
+
101
+ /// <returns></returns>
102
+ [ NotNull , MustUseReturnValue ]
103
+ public string GetBrowsableConnectionString ( bool includeNonBrowsable = false )
104
+ {
105
+ var builder = new StringBuilder ( ) ;
106
+ foreach ( var browsablePair in Keywords )
107
+ {
108
+ if ( ! browsablePair . Value . IsBrowsable && ! includeNonBrowsable )
109
+ continue ;
110
+
111
+ if ( ShouldSerialize ( browsablePair . Key ) && TryGetValue ( browsablePair . Key , out var value ) )
112
+ {
113
+ if ( ! browsablePair . Value . IsBrowsable )
114
+ value = _nonBrowsableValue ;
115
+ var keyValue = Convert . ToString ( value , CultureInfo . InvariantCulture ) ;
116
+ AppendKeyValuePair ( builder , browsablePair . Key , keyValue ) ;
117
+ }
118
+ }
119
+
120
+ return builder . ToString ( ) ;
121
+ }
122
+
123
+ public string GetStringValue ( string keyword ) => ( string ) this [ keyword ] ;
124
+
125
+ public bool TryGetStringValue ( string keyword , out string value )
126
+ {
127
+ value = GetStringValue ( keyword ) ;
128
+ return value != null ;
129
+ }
130
+
131
+ #region Use only allowed keywords
132
+ /// <inheritdoc />
133
+ public override object this [ string keyword ]
134
+ {
135
+ get
136
+ {
137
+ if ( Keywords . ContainsKey ( keyword ) )
138
+ {
139
+ TryGetValue ( keyword , out var value ) ;
140
+ return value ;
141
+ }
142
+ return base [ keyword ] ; // exception for not supported keyword.
143
+ }
144
+ set
145
+ {
146
+ if ( Keywords . TryGetValue ( keyword , out var descriptor ) )
147
+ {
148
+ base [ descriptor . Name ] = value switch
149
+ {
150
+ DateTimeOffset x => x . ToInvariantString ( ) ,
151
+ Guid x => x . ToInvariantString ( ) ,
152
+ #if NET40_OR_GREATER || TARGETS_NETSTANDARD || TARGETS_NETCOREAPP
153
+ TimeSpan x => x . ToInvariantString ( ) ,
154
+ #else
155
+ TimeSpan x => x . ToString ( ) ,
156
+ #endif
157
+ Uri x => x. ToString ( ) ,
158
+ _ => value
159
+ } ;
160
+ }
161
+ }
162
+ }
163
+ #endregion
164
+ }
165
+ }
166
+ }
0 commit comments