/// <summary> /// Configures the builder to match any request whose HTTP content meets the criteria defined by the specified predicate. /// </summary> /// <param name="builder">The <see cref="HttpRequestInterceptionBuilder"/> to use.</param> /// <param name="predicate"> /// A delegate to a method which returns <see langword="true"/> if the /// request's HTTP content is considered a match; otherwise <see langword="false"/>. /// </param> /// <returns> /// The current <see cref="HttpRequestInterceptionBuilder"/>. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="builder"/> is <see langword="null"/>. /// </exception> /// <remarks> /// Pass a value of <see langword="null"/> to remove a previously-registered custom content matching predicate. /// </remarks> public static HttpRequestInterceptionBuilder ForContent( this HttpRequestInterceptionBuilder builder, Predicate <HttpContent>?predicate) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (predicate == null) { return(builder.ForContent(null)); } else { return(builder.ForContent((content) => Task.FromResult(predicate(content)))); } }
/// <summary> /// Sets the parameters to use as the form URL-encoded response content. /// </summary> /// <param name="builder">The <see cref="HttpRequestInterceptionBuilder"/> to use.</param> /// <param name="parameters">The parameters to use for the form URL-encoded content.</param> /// <returns> /// The value specified by <paramref name="builder"/>. /// </returns> /// <exception cref="ArgumentNullException"> /// <paramref name="builder"/> or <paramref name="parameters"/> is <see langword="null"/>. /// </exception> /// <remarks> /// <para> /// Matching is based on a subset rather than an exact match. If all the parameters and values /// specified are found in the request's body the request will be considered a match, even if /// there are other parameters contained in the request's body. Value matching is case-sensitive. /// </para> /// <para> /// For more specific matching to the request body parameters, use the <see cref="HttpRequestInterceptionBuilder.ForContent(Func{HttpContent, Task{bool}})"/> /// method to provide a custom predicate that implements your content matching requirements. /// </para> /// </remarks> public static HttpRequestInterceptionBuilder ForFormContent( this HttpRequestInterceptionBuilder builder, IEnumerable <KeyValuePair <string, string> > parameters) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (parameters == null) { throw new ArgumentNullException(nameof(parameters)); } async Task <bool> IsMatchAsync(HttpContent content) { // FormUrlEncodedContent derives from ByteArrayContent so use the // least specific type for more flexibility (e.g. also StringContent). // Other content types are not supported as they may be iterating over // a stream which might not support arbitrary seeking if the request // is not a match and needs to be sent to the URL originally specified. if (!(content is ByteArrayContent)) { return(false); } string bodyMaybeForm; using (var stream = new MemoryStream()) { await content.CopyToAsync(stream).ConfigureAwait(false); bodyMaybeForm = Encoding.UTF8.GetString(stream.ToArray()); } // This is tolerant to non-well-formed content, so even JSON // parses without issues, it then just won't match the input. var query = QueryHelpers.ParseQuery(bodyMaybeForm); bool matched = true; foreach (var pair in parameters) { if (!query.TryGetValue(pair.Key, out var value) || !string.Equals(pair.Value, value.ToString(), StringComparison.Ordinal)) { return(false); } } return(matched); } return(builder.ForContent(IsMatchAsync)); }