@@ -102,8 +102,13 @@ private static void SetExceptionHandlerFeatures(ErrorContext errorContext)
102
102
/// </summary>
103
103
/// <param name="context"></param>
104
104
/// <returns></returns>
105
+ [ DebuggerDisableUserUnhandledExceptions ]
105
106
public async Task Invoke ( HttpContext context )
106
107
{
108
+ // We want to avoid treating exceptions as user-unhandled if an exception filter like the DatabaseDeveloperPageExceptionFilter
109
+ // handles the exception rather than letting it flow to the default DisplayException method. This is because we don't want to stop the
110
+ // debugger if the developer shouldn't be handling the exception and instead just needs to do something like click a link to run a
111
+ // database migration.
107
112
try
108
113
{
109
114
await _next ( context ) ;
@@ -122,6 +127,11 @@ public async Task Invoke(HttpContext context)
122
127
context . Response . StatusCode = StatusCodes . Status499ClientClosedRequest ;
123
128
}
124
129
130
+ // Generally speaking, we do not expect application code to handle things like IOExceptions during a request
131
+ // body read due to a client disconnect. But aborted requests should be rare in development, and developers
132
+ // might be surprised if an IOException propagating through their code was not considered user-unhandled.
133
+ // That said, if developers complain, we consider removing the following line.
134
+ Debugger . BreakForUserUnhandledException ( ex ) ;
125
135
return ;
126
136
}
127
137
@@ -131,6 +141,8 @@ public async Task Invoke(HttpContext context)
131
141
{
132
142
_logger . ResponseStartedErrorPageMiddleware ( ) ;
133
143
_metrics . RequestException ( exceptionName , ExceptionResult . Skipped , handler : null ) ;
144
+
145
+ // Rethrowing informs the debugger that this exception should be considered user-unhandled.
134
146
throw ;
135
147
}
136
148
@@ -161,11 +173,16 @@ public async Task Invoke(HttpContext context)
161
173
}
162
174
catch ( Exception ex2 )
163
175
{
176
+ // It might make sense to call BreakForUserUnhandledException for ex2 after we do the same for the original exception.
177
+ // But for now, considering the rarity of user-defined IDeveloperPageExceptionFilters, we're not for simplicity.
178
+
164
179
// If there's a Exception while generating the error page, re-throw the original exception.
165
180
_logger . DisplayErrorPageException ( ex2 ) ;
166
181
}
167
182
168
183
_metrics . RequestException ( exceptionName , ExceptionResult . Unhandled , handler : null ) ;
184
+
185
+ // Rethrowing informs the debugger that this exception should be considered user-unhandled.
169
186
throw ;
170
187
}
171
188
@@ -178,6 +195,9 @@ public async Task Invoke(HttpContext context)
178
195
// Assumes the response headers have not been sent. If they have, still attempt to write to the body.
179
196
private Task DisplayException ( ErrorContext errorContext )
180
197
{
198
+ // We need to inform the debugger that this exception should be considered user-unhandled since it wasn't fully handled by an exception filter.
199
+ Debugger . BreakForUserUnhandledException ( errorContext . Exception ) ;
200
+
181
201
var httpContext = errorContext . HttpContext ;
182
202
var headers = httpContext . Request . GetTypedHeaders ( ) ;
183
203
var acceptHeader = headers . Accept ;
0 commit comments