-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
From @BenjaminCharlton on Sunday, June 30, 2019 3:16:27 PM
Is your feature request related to a problem? Please describe.
There are some very useful extension methods for the System.Net.Http.HttpClient found in Microsoft.AspNetCore.Components.HttpClientJsonExtensions. These are used extensively in Blazor apps but are also useful in Console and Windows Forms apps, in fact, any app that interacts with a remote server.
However, with a small change, they could be more useful. They're a bit of a mysterious black-box at the moment. If you don't can't serialize the response from the server into the JSON type T passed as a parameter, it's hard for a client to know what went wrong. It would be much nicer if I could pass an optional delegate (or Func, or Action) to these methods to say what I'd like to do with the response if it can't be serialized to type T. If that delegate could take, as a parameter, the entire HttpResponseMessage object, it would have everything it needed to process the unexpected response gracefully.
Describe the solution you'd like
I think that this, below, or something like it, would work nicely and not break anything.
Amend the class in the file AspNetCore/src/Components/Components/src/HttpClientJsonExtensions.cs to read:
public static class HttpClientJsonExtensions
{
/// <summary>
/// Sends a GET request to the specified URI, and parses the JSON response body
/// to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="actionOnFailure">An action to be executed if the response from the server cannot be
/// serialized into the expected type <typeparamref name="T"/></param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static async Task<T> GetJsonAsync<T>(this HttpClient httpClient, string requestUri, Action<string> actionOnFailure = null)
{
var stringContent = await httpClient.GetStringAsync(requestUri);
T result = default;
try
{
result = JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
}
catch
{
if (actionOnFailure != null)
{
actionOnFailure.Invoke(stringContent);
}
}
return result;
}
/// <summary>
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <param name="actionOnFailure">An action to be executed if the response from the server cannot be
/// serialized into the expected type <typeparamref name="T"/></param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static Task PostJsonAsync(this HttpClient httpClient,
string requestUri,
object content,
Action<HttpResponseMessage> actionOnFailure = null)
=> httpClient.SendJsonAsync(HttpMethod.Post, requestUri, content);
/// <summary>
/// Sends a POST request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <param name="actionOnFailure">An action to be executed if the response from the server cannot be
/// serialized into the expected type <typeparamref name="T"/></param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static Task<T> PostJsonAsync<T>(this HttpClient httpClient,
string requestUri,
object content,
Action<HttpResponseMessage> actionOnFailure = null)
=> httpClient.SendJsonAsync<T>(HttpMethod.Post, requestUri, content, actionOnFailure);
/// <summary>
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <param name="actionOnFailure">An action to be executed if the response from the server cannot be
/// serialized into the expected type <typeparamref name="T"/></param>
public static Task PutJsonAsync(this HttpClient httpClient,
string requestUri,
object content,
Action<HttpResponseMessage> actionOnFailure = null)
=> httpClient.SendJsonAsync(HttpMethod.Put, requestUri, content, actionOnFailure);
/// <summary>
/// Sends a PUT request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <param name="actionOnFailure">An action to be executed if the response from the server cannot be
/// serialized into the expected type <typeparamref name="T"/></param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static Task<T> PutJsonAsync<T>(this HttpClient httpClient,
string requestUri,
object content,
Action<HttpResponseMessage> actionOnFailure = null)
=> httpClient.SendJsonAsync<T>(HttpMethod.Put, requestUri, content, actionOnFailure);
/// <summary>
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format.
/// </summary>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="method">The HTTP method.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="actionOnFailure">An action to be executed if the response from the server cannot be
/// serialized into the expected type <typeparamref name="T"/></param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
public static Task SendJsonAsync(this HttpClient httpClient,
HttpMethod method,
string requestUri,
object content,
Action<HttpResponseMessage> actionOnFailure = null)
=> httpClient.SendJsonAsync<IgnoreResponse>(method, requestUri, content, actionOnFailure);
/// <summary>
/// Sends an HTTP request to the specified URI, including the specified <paramref name="content"/>
/// in JSON-encoded format, and parses the JSON response body to create an object of the generic type.
/// </summary>
/// <typeparam name="T">A type into which the response body can be JSON-deserialized.</typeparam>
/// <param name="httpClient">The <see cref="HttpClient"/>.</param>
/// <param name="method">The HTTP method.</param>
/// <param name="requestUri">The URI that the request will be sent to.</param>
/// <param name="content">Content for the request body. This will be JSON-encoded and sent as a string.</param>
/// <param name="actionOnFailure">An action to be executed if the response from the server cannot be
/// serialized into the expected type <typeparamref name="T"/></param>
/// <returns>The response parsed as an object of the generic type.</returns>
public static async Task<T> SendJsonAsync<T>(this HttpClient httpClient,
HttpMethod method,
string requestUri,
object content,
Action<HttpResponseMessage> actionOnFailure = null)
{
var requestJson = JsonSerializer.Serialize(content, JsonSerializerOptionsProvider.Options);
var response = await httpClient.SendAsync(new HttpRequestMessage(method, requestUri)
{
Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
});
if (actionOnFailure is null)
{
// Make sure the call was successful before we
// attempt to process the response content
response.EnsureSuccessStatusCode();
}
if (typeof(T) == typeof(IgnoreResponse))
{
return default;
}
else
{
var stringContent = await response.Content.ReadAsStringAsync();
try
{
return JsonSerializer.Deserialize<T>(stringContent, JsonSerializerOptionsProvider.Options);
}
catch
{
actionOnFailure.Invoke(response);
return default;
}
}
}
private class IgnoreResponse { }
}Additional context
To use this new feature, we can now say:
var client = new HttpClient();
client.PostJsonAsync<Book>(
"http://bookwebsite.com/api/getbook",
request,
SomethingToDoWhenItDoesntWork)
private void SomethingToDoWhenItDoesntWork(HttpResponseMessage response)
{
// We can read the response from the server here, log it or do something else with it.
}
Copied from original issue: dotnet/aspnetcore#11725