in my Blazor WASM app, I have a file download feature. When calling my API to download a specific file - the API calls to blob storage where the file is saved and returns it to the client.
Since the blobs might be bigger, I want to minimize memory consumption on the API and therefore, I want to get the blob as a stream and pass this through to the client.
I am using the Azure Storage SDK to interact with blob storage. My API returns this error
System.InvalidOperationException: Timeouts are not supported on this stream.
at System.IO.Stream.get_ReadTimeout()
at System.Text.Json.Serialization.Metadata.JsonPropertyInfo`1.GetMemberAndWriteJson(Object obj, WriteStack& state, Utf8JsonWriter writer)
at System.Text.Json.Serialization.Converters.ObjectDefaultConverter`1.OnTryWrite(Utf8JsonWriter writer, T value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.TryWrite(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1.WriteCore(Utf8JsonWriter writer, T& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed)
at System.Text.Json.Serialization.Metadata.JsonTypeInfo`1.SerializeAsync(Stream utf8Json, T rootValue, CancellationToken cancellationToken, Object rootValueBoxed)
at Microsoft.AspNetCore.Http.HttpResponseJsonExtensions.WriteAsJsonAsyncSlow[TValue](Stream body, TValue value, JsonSerializerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Http.RequestDelegateFactory.ExecuteTaskResult[T](Task`1 task, HttpContext httpContext)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
Here's the code
minimal api endpoint
app.MapGet("/download/{container}/{blob}", async (HttpContext ctx, [FromServices] IBlobService blobSvc, string container, string blob) =>
{
var b = await blobSvc.GetBlobStream(container, blob);
return Results.Ok(b);
})
.RequireAuthorization(AuthzPolicy.Policy)
;
blob service / using Azure Storage SDK
public class BlobService : IBlobService
{
readonly BlobServiceClient _svcClient;
BlobContainerClient _getContainerClient(string containerName) => _svcClient.GetBlobContainerClient(containerName);
BlobClient _getBlobClient(string containerName, string file) => _getContainerClient(containerName).GetBlobClient(file);
public BlobService(string blobEndpoint)
{
var cred = new DefaultAzureCredential();
_svcClient = new (new Uri (blobEndpoint), cred);
}
public async Task<Stream> GetBlobStream(string containerName, string file)
{
var blobClient = _getBlobClient(containerName, file);
return await blobClient.OpenReadAsync();
}
}
Blazor WASM client code to download
async Task _download(string container, string blob)
{
HttpClient http = httpFactory.CreateClient(HttpClientName.REST);
HttpResponseMessage resp = await http.GetAsync($"/download/{container}/{blob}");
if (!resp.IsSuccessStatusCode)
{
throw new ApplicationException(JsonSerializer.Serialize(resp.ReasonPhrase));
}
// JS interop due to WASM
using var streamRef = new Microsoft.JSInterop.DotNetStreamReference(await resp.Content.ReadAsStreamAsync());
await _js.InvokeVoidAsync("download", blob, streamRef);
}
Javascript interop
async function download(fileName, fileStream) {
const arrayBuffer = await fileStream.arrayBuffer();
const blob = new Blob([arrayBuffer]);
const url = URL.createObjectURL(blob);
triggerDownload(fileName, url);
URL.revokeObjectURL(url);
}
function triggerDownload(fielName, url) {
const anchorElement = document.createElement("a");
anchorElement.href = url;
anchorElement.download = fielName;
anchorElement.click();
anchorElement.remove();
}
Do you see something wrong with this? I am getting the same error when I just use Powershell to request the download via the following, which makes me think its not related to the client code, but rather the fault is in the API layer.
iwr https://localhost:7029/download/container/blob
Any help with this is greatly appreciated.
Please try to use Results.File
instead of Results.Ok
.
And change your GetBlobStream
like below.
public async Task<Stream> GetBlobStream(string containerName, string file)
{
var blobClient = _getBlobClient(containerName, file);
return await blobClient.OpenReadAsync(new BlobOpenReadOptions(false));
}