Skip to content

Commit 6fdde36

Browse files
committed
Throw unhandled exceptions during prerendering
Fixes: #8609 Currently exceptions thrown during prerendering are simply logged. This change uses the existing *unhandled exception* mechanism of the renderer/circuit to throw these. The result is that the developer exception page just works for prerendering.
1 parent 9f1a978 commit 6fdde36

File tree

10 files changed

+515
-70
lines changed

10 files changed

+515
-70
lines changed

src/Components/Components/ref/Microsoft.AspNetCore.Components.netstandard2.0.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public static partial class EventCallbackFactoryBinderExtensions
155155
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<float?> setter, float? existingValue) { throw null; }
156156
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<float> setter, float existingValue) { throw null; }
157157
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<string> setter, string existingValue) { throw null; }
158-
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder<T>(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<T> setter, T existingValue) where T : System.Enum { throw null; }
158+
public static Microsoft.AspNetCore.Components.EventCallback<Microsoft.AspNetCore.Components.UIChangeEventArgs> CreateBinder<T>(this Microsoft.AspNetCore.Components.EventCallbackFactory factory, object receiver, System.Action<T> setter, T existingValue) where T : struct, System.Enum { throw null; }
159159
}
160160
public static partial class EventCallbackFactoryUIEventArgsExtensions
161161
{

src/Components/Components/src/EventCallbackFactoryBinderExtensions.cs

Lines changed: 277 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)