@@ -11,74 +11,263 @@ namespace Microsoft.AspNetCore.Components
11
11
/// </summary>
12
12
public static class EventCallbackFactoryBinderExtensions
13
13
{
14
+ private delegate bool BindConverter < T > ( object obj , out T value ) ;
15
+
14
16
// Perf: conversion delegates are written as static funcs so we can prevent
15
17
// allocations for these simple cases.
16
- private static Func < object , string > ConvertToString = ( obj ) => ( string ) obj ;
18
+ private readonly static BindConverter < string > ConvertToString = ConvertToStringCore ;
17
19
18
- private static Func < object , bool > ConvertToBool = ( obj ) => ( bool ) obj ;
19
- private static Func < object , bool ? > ConvertToNullableBool = ( obj ) => ( bool ? ) obj ;
20
+ private static bool ConvertToStringCore ( object obj , out string value )
21
+ {
22
+ // We expect the input to already be a string.
23
+ value = ( string ) obj ;
24
+ return true ;
25
+ }
20
26
21
- private static Func < object , int > ConvertToInt = ( obj ) => int . Parse ( ( string ) obj ) ;
22
- private static Func < object , int ? > ConvertToNullableInt = ( obj ) =>
27
+ private static BindConverter < bool > ConvertToBool = ConvertToBoolCore ;
28
+ private static BindConverter < bool ? > ConvertToNullableBool = ConvertToNullableBoolCore ;
29
+
30
+ private static bool ConvertToBoolCore ( object obj , out bool value )
23
31
{
24
- if ( int . TryParse ( ( string ) obj , out var value ) )
32
+ // We expect the input to already be a bool.
33
+ value = ( bool ) obj ;
34
+ return true ;
35
+ }
36
+
37
+ private static bool ConvertToNullableBoolCore ( object obj , out bool ? value )
38
+ {
39
+ // We expect the input to already be a bool.
40
+ value = ( bool ? ) obj ;
41
+ return true ;
42
+ }
43
+
44
+ private static BindConverter < int > ConvertToInt = ConvertToIntCore ;
45
+ private static BindConverter < int ? > ConvertToNullableInt = ConvertToNullableIntCore ;
46
+
47
+ private static bool ConvertToIntCore ( object obj , out int value )
48
+ {
49
+ var text = ( string ) obj ;
50
+ if ( string . IsNullOrEmpty ( text ) )
25
51
{
26
- return value ;
52
+ value = default ;
53
+ return false ;
54
+ }
55
+
56
+ if ( ! int . TryParse ( text , out var converted ) )
57
+ {
58
+ value = default ;
59
+ return false ;
27
60
}
28
61
29
- return null ;
30
- } ;
62
+ value = converted ;
63
+ return true ;
64
+ }
31
65
32
- private static Func < object , long > ConvertToLong = ( obj ) => long . Parse ( ( string ) obj ) ;
33
- private static Func < object , long ? > ConvertToNullableLong = ( obj ) =>
66
+ private static bool ConvertToNullableIntCore ( object obj , out int ? value )
34
67
{
35
- if ( long . TryParse ( ( string ) obj , out var value ) )
68
+ var text = ( string ) obj ;
69
+ if ( string . IsNullOrEmpty ( text ) )
36
70
{
37
- return value ;
71
+ value = default ;
72
+ return true ;
38
73
}
39
74
40
- return null ;
41
- } ;
75
+ if ( ! int . TryParse ( text , out var converted ) )
76
+ {
77
+ value = default ;
78
+ return false ;
79
+ }
80
+
81
+ value = converted ;
82
+ return true ;
83
+ }
84
+
85
+ private static BindConverter < long > ConvertToLong = ConvertToLongCore ;
86
+ private static BindConverter < long ? > ConvertToNullableLong = ConvertToNullableLongCore ;
42
87
43
- private static Func < object , float > ConvertToFloat = ( obj ) => float . Parse ( ( string ) obj ) ;
44
- private static Func < object , float ? > ConvertToNullableFloat = ( obj ) =>
88
+ private static bool ConvertToLongCore ( object obj , out long value )
45
89
{
46
- if ( float . TryParse ( ( string ) obj , out var value ) )
90
+ var text = ( string ) obj ;
91
+ if ( string . IsNullOrEmpty ( text ) )
47
92
{
48
- return value ;
93
+ value = default ;
94
+ return false ;
95
+ }
96
+
97
+ if ( ! long . TryParse ( text , out var converted ) )
98
+ {
99
+ value = default ;
100
+ return false ;
101
+ }
102
+
103
+ value = converted ;
104
+ return true ;
105
+ }
106
+
107
+ private static bool ConvertToNullableLongCore ( object obj , out long ? value )
108
+ {
109
+ var text = ( string ) obj ;
110
+ if ( string . IsNullOrEmpty ( text ) )
111
+ {
112
+ value = default ;
113
+ return true ;
49
114
}
50
115
51
- return null ;
52
- } ;
116
+ if ( ! long . TryParse ( text , out var converted ) )
117
+ {
118
+ value = default ;
119
+ return false ;
120
+ }
53
121
54
- private static Func < object , double > ConvertToDouble = ( obj ) => double . Parse ( ( string ) obj ) ;
55
- private static Func < object , double ? > ConvertToNullableDouble = ( obj ) =>
122
+ value = converted ;
123
+ return true ;
124
+ }
125
+
126
+ private static BindConverter < float > ConvertToFloat = ConvertToFloatCore ;
127
+ private static BindConverter < float ? > ConvertToNullableFloat = ConvertToNullableFloatCore ;
128
+
129
+ private static bool ConvertToFloatCore ( object obj , out float value )
56
130
{
57
- if ( double . TryParse ( ( string ) obj , out var value ) )
131
+ var text = ( string ) obj ;
132
+ if ( string . IsNullOrEmpty ( text ) )
58
133
{
59
- return value ;
134
+ value = default ;
135
+ return false ;
136
+ }
137
+
138
+ if ( ! float . TryParse ( text , out var converted ) )
139
+ {
140
+ value = default ;
141
+ return false ;
60
142
}
61
143
62
- return null ;
63
- } ;
144
+ value = converted ;
145
+ return true ;
146
+ }
64
147
65
- private static Func < object , decimal > ConvertToDecimal = ( obj ) => decimal . Parse ( ( string ) obj ) ;
66
- private static Func < object , decimal ? > ConvertToNullableDecimal = ( obj ) =>
148
+ private static bool ConvertToNullableFloatCore ( object obj , out float ? value )
67
149
{
68
- if ( decimal . TryParse ( ( string ) obj , out var value ) )
150
+ var text = ( string ) obj ;
151
+ if ( string . IsNullOrEmpty ( text ) )
69
152
{
70
- return value ;
153
+ value = default ;
154
+ return true ;
71
155
}
72
156
73
- return null ;
74
- } ;
157
+ if ( ! float . TryParse ( text , out var converted ) )
158
+ {
159
+ value = default ;
160
+ return false ;
161
+ }
162
+
163
+ value = converted ;
164
+ return true ;
165
+ }
166
+
167
+ private static BindConverter < double > ConvertToDouble = ConvertToDoubleCore ;
168
+ private static BindConverter < double ? > ConvertToNullableDouble = ConvertToNullableDoubleCore ;
75
169
76
- private static class EnumConverter < T > where T : Enum
170
+ private static bool ConvertToDoubleCore ( object obj , out double value )
77
171
{
78
- public static Func < object , T > Convert = ( obj ) =>
172
+ var text = ( string ) obj ;
173
+ if ( string . IsNullOrEmpty ( text ) )
79
174
{
80
- return ( T ) Enum . Parse ( typeof ( T ) , ( string ) obj ) ;
81
- } ;
175
+ value = default ;
176
+ return false ;
177
+ }
178
+
179
+ if ( ! double . TryParse ( text , out var converted ) )
180
+ {
181
+ value = default ;
182
+ return false ;
183
+ }
184
+
185
+ value = converted ;
186
+ return true ;
187
+ }
188
+
189
+ private static bool ConvertToNullableDoubleCore ( object obj , out double ? value )
190
+ {
191
+ var text = ( string ) obj ;
192
+ if ( string . IsNullOrEmpty ( text ) )
193
+ {
194
+ value = default ;
195
+ return true ;
196
+ }
197
+
198
+ if ( ! double . TryParse ( text , out var converted ) )
199
+ {
200
+ value = default ;
201
+ return false ;
202
+ }
203
+
204
+ value = converted ;
205
+ return true ;
206
+ }
207
+
208
+ private static BindConverter < decimal > ConvertToDecimal = ConvertToDecimalCore ;
209
+ private static BindConverter < decimal ? > ConvertToNullableDecimal = ConvertToNullableDecimalCore ;
210
+
211
+ private static bool ConvertToDecimalCore ( object obj , out decimal value )
212
+ {
213
+ var text = ( string ) obj ;
214
+ if ( string . IsNullOrEmpty ( text ) )
215
+ {
216
+ value = default ;
217
+ return false ;
218
+ }
219
+
220
+ if ( ! decimal . TryParse ( text , out var converted ) )
221
+ {
222
+ value = default ;
223
+ return false ;
224
+ }
225
+
226
+ value = converted ;
227
+ return true ;
228
+ }
229
+
230
+ private static bool ConvertToNullableDecimalCore ( object obj , out decimal ? value )
231
+ {
232
+ var text = ( string ) obj ;
233
+ if ( string . IsNullOrEmpty ( text ) )
234
+ {
235
+ value = default ;
236
+ return true ;
237
+ }
238
+
239
+ if ( ! decimal . TryParse ( text , out var converted ) )
240
+ {
241
+ value = default ;
242
+ return false ;
243
+ }
244
+
245
+ value = converted ;
246
+ return true ;
247
+ }
248
+
249
+ private static class EnumConverter < T > where T : struct , Enum
250
+ {
251
+ public static readonly BindConverter < T > Convert = ConvertCore ;
252
+
253
+ public static bool ConvertCore ( object obj , out T value )
254
+ {
255
+ var text = ( string ) obj ;
256
+ if ( string . IsNullOrEmpty ( text ) )
257
+ {
258
+ value = default ;
259
+ return true ;
260
+ }
261
+
262
+ if ( ! Enum . TryParse < T > ( text , out var converted ) )
263
+ {
264
+ value = default ;
265
+ return false ;
266
+ }
267
+
268
+ value = converted ;
269
+ return true ;
270
+ }
82
271
}
83
272
84
273
/// <summary>
@@ -330,7 +519,22 @@ public static EventCallback<UIChangeEventArgs> CreateBinder(
330
519
// when a format is used.
331
520
Action < UIChangeEventArgs > callback = ( e ) =>
332
521
{
333
- setter ( ConvertDateTime ( e . Value , format : null ) ) ;
522
+ DateTime value = default ;
523
+ var converted = false ;
524
+ try
525
+ {
526
+ value = ConvertDateTime ( e . Value , format : null ) ;
527
+ converted = true ;
528
+ }
529
+ catch
530
+ {
531
+ }
532
+
533
+ // See comments in CreateBinderCore
534
+ if ( converted )
535
+ {
536
+ setter ( value ) ;
537
+ }
334
538
} ;
335
539
return factory . Create < UIChangeEventArgs > ( receiver , callback ) ;
336
540
}
@@ -355,7 +559,22 @@ public static EventCallback<UIChangeEventArgs> CreateBinder(
355
559
// when a format is used.
356
560
Action < UIChangeEventArgs > callback = ( e ) =>
357
561
{
358
- setter ( ConvertDateTime ( e . Value , format ) ) ;
562
+ DateTime value = default ;
563
+ var converted = false ;
564
+ try
565
+ {
566
+ value = ConvertDateTime ( e . Value , format ) ;
567
+ converted = true ;
568
+ }
569
+ catch
570
+ {
571
+ }
572
+
573
+ // See comments in CreateBinderCore
574
+ if ( converted )
575
+ {
576
+ setter ( value ) ;
577
+ }
359
578
} ;
360
579
return factory . Create < UIChangeEventArgs > ( receiver , callback ) ;
361
580
}
@@ -373,7 +592,7 @@ public static EventCallback<UIChangeEventArgs> CreateBinder<T>(
373
592
this EventCallbackFactory factory ,
374
593
object receiver ,
375
594
Action < T > setter ,
376
- T existingValue ) where T : Enum
595
+ T existingValue ) where T : struct , Enum
377
596
{
378
597
return CreateBinderCore < T > ( factory , receiver , setter , EnumConverter < T > . Convert ) ;
379
598
}
@@ -399,11 +618,27 @@ private static EventCallback<UIChangeEventArgs> CreateBinderCore<T>(
399
618
this EventCallbackFactory factory ,
400
619
object receiver ,
401
620
Action < T > setter ,
402
- Func < object , T > converter )
621
+ BindConverter < T > converter )
403
622
{
404
623
Action < UIChangeEventArgs > callback = e =>
405
624
{
406
- setter ( converter ( e . Value ) ) ;
625
+ T value = default ;
626
+ var converted = false ;
627
+ try
628
+ {
629
+ converted = converter ( e . Value , out value ) ;
630
+ }
631
+ catch
632
+ {
633
+ }
634
+
635
+ // We only invoke the setter if the conversion didn't throw. This is valuable because it allows us to attempt
636
+ // to process invalid input but avoid dirtying the state of the component if can't be converted. Imagine if
637
+ // we assigned default(T) on failure - this would result in trouncing the user's typed in value.
638
+ if ( converted )
639
+ {
640
+ setter ( value ) ;
641
+ }
407
642
} ;
408
643
return factory . Create < UIChangeEventArgs > ( receiver , callback ) ;
409
644
}
0 commit comments