@@ -105,11 +105,10 @@ public HttpClientInterceptorOptions Clone()
105
105
OnMissingRegistration = OnMissingRegistration ,
106
106
OnSend = OnSend ,
107
107
ThrowOnMissingRegistration = ThrowOnMissingRegistration ,
108
+ _comparer = _comparer ,
109
+ _mappings = new ConcurrentDictionary < string , HttpInterceptionResponse > ( _mappings , _comparer ) ,
108
110
} ;
109
111
110
- clone . _comparer = _comparer ;
111
- clone . _mappings = new ConcurrentDictionary < string , HttpInterceptionResponse > ( _mappings , _comparer ) ;
112
-
113
112
return clone ;
114
113
}
115
114
@@ -158,6 +157,9 @@ public HttpClientInterceptorOptions Deregister(HttpMethod method, Uri uri)
158
157
/// <exception cref="ArgumentNullException">
159
158
/// <paramref name="builder"/> is <see langword="null"/>.
160
159
/// </exception>
160
+ /// <exception cref="InvalidOperationException">
161
+ /// The HTTP registration associated with <paramref name="builder"/> could not be deregistered.
162
+ /// </exception>
161
163
/// <remarks>
162
164
/// If <paramref name="builder"/> has been reconfigured since it was used
163
165
/// to register a previous HTTP request interception it will not remove that
@@ -170,10 +172,22 @@ public HttpClientInterceptorOptions Deregister(HttpRequestInterceptionBuilder bu
170
172
throw new ArgumentNullException ( nameof ( builder ) ) ;
171
173
}
172
174
173
- HttpInterceptionResponse interceptor = builder . Build ( ) ;
175
+ // Use any key stored in the builder to deregister the interception,
176
+ // if available, otherwise rebuild from the builder itself.
177
+ string ? key = builder . Key ;
174
178
175
- string key = BuildKey ( interceptor ) ;
176
- _mappings . Remove ( key ) ;
179
+ if ( key is null )
180
+ {
181
+ HttpInterceptionResponse interceptor = builder . Build ( ) ;
182
+ key = BuildKey ( interceptor ) ;
183
+ }
184
+
185
+ bool removed = _mappings . Remove ( key ) ;
186
+
187
+ if ( ! removed )
188
+ {
189
+ throw new InvalidOperationException ( "Failed to deregister HTTP request interception. The builder has not been used to register an HTTP request or has been mutated since it was registered." ) ;
190
+ }
177
191
178
192
return this ;
179
193
}
@@ -232,7 +246,7 @@ public HttpClientInterceptorOptions RegisterByteArray(
232
246
StatusCode = statusCode ,
233
247
} ;
234
248
235
- ConfigureMatcherAndRegister ( interceptor ) ;
249
+ _ = ConfigureMatcherAndRegister ( interceptor ) ;
236
250
237
251
return this ;
238
252
}
@@ -291,7 +305,7 @@ public HttpClientInterceptorOptions RegisterStream(
291
305
StatusCode = statusCode ,
292
306
} ;
293
307
294
- ConfigureMatcherAndRegister ( interceptor ) ;
308
+ _ = ConfigureMatcherAndRegister ( interceptor ) ;
295
309
296
310
return this ;
297
311
}
@@ -315,7 +329,11 @@ public HttpClientInterceptorOptions Register(HttpRequestInterceptionBuilder buil
315
329
316
330
HttpInterceptionResponse interceptor = builder . Build ( ) ;
317
331
318
- ConfigureMatcherAndRegister ( interceptor ) ;
332
+ string key = ConfigureMatcherAndRegister ( interceptor ) ;
333
+
334
+ // Store the key so deregistration for the builder works if
335
+ // it is not mutated after the registration has occurred.
336
+ builder . SetMatchKey ( key ) ;
319
337
320
338
return this ;
321
339
}
@@ -390,8 +408,8 @@ private static string BuildKey(HttpInterceptionResponse interceptor)
390
408
if ( interceptor . UserMatcher is not null || interceptor . ContentMatcher is not null )
391
409
{
392
410
// Use the internal matcher's hash code as UserMatcher (a delegate)
393
- // will always return the hash code. See https://stackoverflow.com/q/6624151/1064169
394
- return $ "CUSTOM:{ interceptor . InternalMatcher ! . GetHashCode ( ) . ToString ( CultureInfo . InvariantCulture ) } ";
411
+ // will always return the same hash code. See https://stackoverflow.com/q/6624151/1064169
412
+ return $ "CUSTOM:{ interceptor . InternalMatcher ? . GetHashCode ( ) . ToString ( CultureInfo . InvariantCulture ) } ";
395
413
}
396
414
397
415
var builderForKey = new UriBuilder ( interceptor . RequestUri ! ) ;
@@ -498,7 +516,7 @@ private static async Task<HttpResponseMessage> BuildResponseAsync(HttpRequestMes
498
516
return new ( false , null ) ;
499
517
}
500
518
501
- private void ConfigureMatcherAndRegister ( HttpInterceptionResponse registration )
519
+ private string ConfigureMatcherAndRegister ( HttpInterceptionResponse registration )
502
520
{
503
521
RequestMatcher matcher ;
504
522
@@ -514,7 +532,10 @@ private void ConfigureMatcherAndRegister(HttpInterceptionResponse registration)
514
532
registration . InternalMatcher = matcher ;
515
533
516
534
string key = BuildKey ( registration ) ;
535
+
517
536
_mappings [ key ] = registration ;
537
+
538
+ return key ;
518
539
}
519
540
520
541
private sealed class OptionsScope : IDisposable
0 commit comments