For Blazor WASM you need to do the following:
On the server side, add the following code to Program.cs:
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
This code ensures server will handle references as you need.
Note: Instead of AddControllers
you can use also AddControllersWithViews
On the client side (Blazor WASM), there is at the moment no way to do it in such a simple way. Or at least I did not find anything. So you need to ensure that for every call of JSON-based API, you will pass a JSON option and use it for deserialization.
You can do it with extension methods like this:
public static class HttpClientExtensions
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve,
PropertyNameCaseInsensitive = true
};
public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient httpClient, string requestUri)
{
return httpClient.GetFromJsonAsync<TValue>(requestUri, JsonSerializerOptions);
}
}
Or you can do it by custom HTTP client class like this:
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
public class JsonHttpClient
{
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonSerializerOptions;
public JsonHttpClient(HttpClient httpClient)
{
_httpClient = httpClient;
_jsonSerializerOptions = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve,
PropertyNameCaseInsensitive = true
};
}
public async Task<T> GetFromJsonAsync<T>(string requestUri)
{
var response = await _httpClient.GetAsync(requestUri);
response.EnsureSuccessStatusCode();
using var contentStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<T>(contentStream, _jsonSerializerOptions);
}
}
and with this second option you also need to add following code to Program.cs
builder.Services.AddScoped(sp => new JsonHttpClient(sp.GetRequiredService<HttpClient>()));
and following code where you want to use it (razor page):
@inject JsonHttpClient JsonHttpClient
and
var resultData = await JsonHttpClient.GetFromJsonAsync<MyDataType>("api/someendpoint");
Note: there is only inject of new HTTP Client and exchange of Http
to JsonHttpClient
.
Here is also code of both classes with other useful methods:
public static class HttpClientExtensions
{
private static readonly JsonSerializerOptions JsonSerializerOptions = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve,
PropertyNameCaseInsensitive = true
};
public static Task<TValue> GetFromJsonAsync<TValue>(this HttpClient httpClient, string requestUri)
{
return httpClient.GetFromJsonAsync<TValue>(requestUri, JsonSerializerOptions);
}
public static Task<HttpResponseMessage> PostAsJsonAsync<TValue>(this HttpClient httpClient, string requestUri, TValue value)
{
return httpClient.PostAsJsonAsync(requestUri, value, JsonSerializerOptions);
}
public static Task<HttpResponseMessage> PutAsJsonAsync<TValue>(this HttpClient httpClient, string requestUri, TValue value)
{
return httpClient.PutAsJsonAsync(requestUri, value, JsonSerializerOptions);
}
public static async Task<TResponse> PostAsJsonAsync<TValue, TResponse>(this HttpClient httpClient, string requestUri, TValue value)
{
var response = await httpClient.PostAsJsonAsync(requestUri, value, JsonSerializerOptions);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<TResponse>(JsonSerializerOptions);
}
public static async Task<TResponse> PutAsJsonAsync<TValue, TResponse>(this HttpClient httpClient, string requestUri, TValue value)
{
var response = await httpClient.PutAsJsonAsync(requestUri, value, JsonSerializerOptions);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<TResponse>(JsonSerializerOptions);
}
}
and
using System.Net.Http;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
public class JsonHttpClient
{
private readonly HttpClient _httpClient;
private readonly JsonSerializerOptions _jsonSerializerOptions;
public JsonHttpClient(HttpClient httpClient)
{
_httpClient = httpClient;
_jsonSerializerOptions = new JsonSerializerOptions
{
ReferenceHandler = ReferenceHandler.Preserve,
PropertyNameCaseInsensitive = true
};
}
public async Task<T> GetFromJsonAsync<T>(string requestUri)
{
var response = await _httpClient.GetAsync(requestUri);
response.EnsureSuccessStatusCode();
using var contentStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<T>(contentStream, _jsonSerializerOptions);
}
public async Task<TResponse> PostAsJsonAsync<TRequest, TResponse>(string requestUri, TRequest content)
{
using var contentStream = new MemoryStream();
await JsonSerializer.SerializeAsync(contentStream, content, _jsonSerializerOptions);
contentStream.Position = 0;
using var response = await _httpClient.PostAsync(requestUri, new StreamContent(contentStream));
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<TResponse>(responseStream, _jsonSerializerOptions);
}
public async Task PostAsJsonAsync<TRequest>(string requestUri, TRequest content)
{
using var contentStream = new MemoryStream();
await JsonSerializer.SerializeAsync(contentStream, content, _jsonSerializerOptions);
contentStream.Position = 0;
using var response = await _httpClient.PostAsync(requestUri, new StreamContent(contentStream));
response.EnsureSuccessStatusCode();
}
public async Task<TResponse> PutAsJsonAsync<TRequest, TResponse>(string requestUri, TRequest content)
{
using var contentStream = new MemoryStream();
await JsonSerializer.SerializeAsync(contentStream, content, _jsonSerializerOptions);
contentStream.Position = 0;
using var response = await _httpClient.PutAsync(requestUri, new StreamContent(contentStream));
response.EnsureSuccessStatusCode();
using var responseStream = await response.Content.ReadAsStreamAsync();
return await JsonSerializer.DeserializeAsync<TResponse>(responseStream, _jsonSerializerOptions);
}
public async Task PutAsJsonAsync<TRequest>(string requestUri, TRequest content)
{
using var contentStream = new MemoryStream();
await JsonSerializer.SerializeAsync(contentStream, content, _jsonSerializerOptions);
contentStream.Position = 0;
using var response = await _httpClient.PutAsync(requestUri, new StreamContent(contentStream));
response.EnsureSuccessStatusCode();
}
public async Task DeleteAsync(string requestUri)
{
var response = await _httpClient.DeleteAsync(requestUri);
response.EnsureSuccessStatusCode();
}
}
ReferenceHandler.Preserve
: dotnetfiddle.net/t2EkHR. Note thatReferenceHandler.Preserve
only works in .Net 5 or later. To diagnose the problem you mention when you wrote I have not been able to implement it successfully to get it to work we need to see a minimal reproducible example with the code you have written that does not work. – UnfriendedPreserveReferencesHandling
functionality does not work with dynamic proxies, one needs to setConfiguration.ProxyCreationEnabled = false;
e,g, as shown here. I'm not familiar with Blazor WebAssembies but maybe something similar is going on here? – Unfriended