@@ -11,74 +11,263 @@ namespace Microsoft.AspNetCore.Components
1111 /// </summary>
1212 public static class EventCallbackFactoryBinderExtensions
1313 {
14+ private delegate bool BindConverter < T > ( object obj , out T value ) ;
15+
1416 // Perf: conversion delegates are written as static funcs so we can prevent
1517 // allocations for these simple cases.
16- private static Func < object , string > ConvertToString = ( obj ) => ( string ) obj ;
18+ private readonly static BindConverter < string > ConvertToString = ConvertToStringCore ;
1719
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+ }
2026
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 )
2331 {
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 ) )
2551 {
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 ;
2760 }
2861
29- return null ;
30- } ;
62+ value = converted ;
63+ return true ;
64+ }
3165
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 )
3467 {
35- if ( long . TryParse ( ( string ) obj , out var value ) )
68+ var text = ( string ) obj ;
69+ if ( string . IsNullOrEmpty ( text ) )
3670 {
37- return value ;
71+ value = default ;
72+ return true ;
3873 }
3974
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 ;
4287
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 )
4589 {
46- if ( float . TryParse ( ( string ) obj , out var value ) )
90+ var text = ( string ) obj ;
91+ if ( string . IsNullOrEmpty ( text ) )
4792 {
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 ;
49114 }
50115
51- return null ;
52- } ;
116+ if ( ! long . TryParse ( text , out var converted ) )
117+ {
118+ value = default ;
119+ return false ;
120+ }
53121
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 )
56130 {
57- if ( double . TryParse ( ( string ) obj , out var value ) )
131+ var text = ( string ) obj ;
132+ if ( string . IsNullOrEmpty ( text ) )
58133 {
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 ;
60142 }
61143
62- return null ;
63- } ;
144+ value = converted ;
145+ return true ;
146+ }
64147
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 )
67149 {
68- if ( decimal . TryParse ( ( string ) obj , out var value ) )
150+ var text = ( string ) obj ;
151+ if ( string . IsNullOrEmpty ( text ) )
69152 {
70- return value ;
153+ value = default ;
154+ return true ;
71155 }
72156
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 ;
75169
76- private static class EnumConverter < T > where T : Enum
170+ private static bool ConvertToDoubleCore ( object obj , out double value )
77171 {
78- public static Func < object , T > Convert = ( obj ) =>
172+ var text = ( string ) obj ;
173+ if ( string . IsNullOrEmpty ( text ) )
79174 {
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+ }
82271 }
83272
84273 /// <summary>
@@ -330,7 +519,22 @@ public static EventCallback<UIChangeEventArgs> CreateBinder(
330519 // when a format is used.
331520 Action < UIChangeEventArgs > callback = ( e ) =>
332521 {
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+ }
334538 } ;
335539 return factory . Create < UIChangeEventArgs > ( receiver , callback ) ;
336540 }
@@ -355,7 +559,22 @@ public static EventCallback<UIChangeEventArgs> CreateBinder(
355559 // when a format is used.
356560 Action < UIChangeEventArgs > callback = ( e ) =>
357561 {
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+ }
359578 } ;
360579 return factory . Create < UIChangeEventArgs > ( receiver , callback ) ;
361580 }
@@ -373,7 +592,7 @@ public static EventCallback<UIChangeEventArgs> CreateBinder<T>(
373592 this EventCallbackFactory factory ,
374593 object receiver ,
375594 Action < T > setter ,
376- T existingValue ) where T : Enum
595+ T existingValue ) where T : struct , Enum
377596 {
378597 return CreateBinderCore < T > ( factory , receiver , setter , EnumConverter < T > . Convert ) ;
379598 }
@@ -399,11 +618,27 @@ private static EventCallback<UIChangeEventArgs> CreateBinderCore<T>(
399618 this EventCallbackFactory factory ,
400619 object receiver ,
401620 Action < T > setter ,
402- Func < object , T > converter )
621+ BindConverter < T > converter )
403622 {
404623 Action < UIChangeEventArgs > callback = e =>
405624 {
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+ }
407642 } ;
408643 return factory . Create < UIChangeEventArgs > ( receiver , callback ) ;
409644 }
0 commit comments