skip to Main Content

I am developing web api for work with files. I am trying to download file from azure blob storage and return it as response. For development api I using FastEndpoints.

On SO I found out that the best option is to use FileStreamResult, but I am gettign error

System.NotSupportedException: Deserialization of types without a
parameterless constructor

How can I resolve it and why I am facing it?

Endpoint

public class Endpoint : Endpoint<DownloadFileRequest, FileStreamResult>
{
    private readonly DatabaseContext _context;
    private readonly ILogger _logger;
    private readonly IConfiguration _configuration;

    public Endpoint(DatabaseContext context, ILogger<Endpoint> logger, IConfiguration configuration)
    {
        _context = context;
        _logger = logger;
        _configuration = configuration;
    }

    public override void Configure()
    {
        Get("/file/{id}/download");
        Roles(Role.Administrator, Role.ProjectManager, Role.GraphicDesigner, Role.Accountant);
        Description(b => b.WithName("DownloadFile"));
    }

    public override async Task HandleAsync(DownloadFileRequest r, CancellationToken c)
    {
        var file = await Data.GetTriadaFileAsync(_context, r.Id, c); // gets file to 
                                                                     // download

        if (file == null)
        {
            await SendNotFoundAsync(c);
            return;
        }
           
        var result = await Data.DownloadBlobAsync(file.AzureName, r, c, _configuration);

        if (result != null)
        {
            Response.FileStream = result.FileStream;
        }
    }
}

Data

public static class Data
{
    public static async Task<TriadaFile?> GetTriadaFileAsync(DatabaseContext context, Guid id, CancellationToken c)
    {
        return await context.Files
            .FirstOrDefaultAsync(x => x.Id == id, c);
    }
    public static async Task<FileStreamResult?> DownloadBlobAsync(string blobFileName, DownloadFileRequest r, CancellationToken c, IConfiguration _configuration)
    {
        string connectionString = _configuration.GetSection("Azure").GetValue<string>("BlobConnectionString")!;
        string containerName = _configuration.GetSection("Azure").GetValue<string>("BlobContainerName")!;

        BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
        BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);

        BlobClient blobClient = containerClient.GetBlobClient(blobFileName);

        using (var stream = new MemoryStream())
        {
            await blobClient.DownloadToAsync(stream);
            stream.Position = 0;
            var contentType = (await blobClient.GetPropertiesAsync()).Value.ContentType;

            var fileStreamResult = new FileStreamResult(stream, "application/octet-stream");

            fileStreamResult.FileDownloadName = blobFileName;

            return fileStreamResult;
        }
    }


}

Models

public class DownloadFileRequest
{
    public Guid Id{ get; set; }
}

public class Validator : Validator<DownloadFileRequest>
{
    public Validator()
    {
        RuleFor(x => x.Id)
            .NotEmpty();
    }
}

public class DownloadFileResponse
{
   

}

Question that helped me with me

2

Answers


  1. Chosen as BEST ANSWER

    The problem was whatever the content type was, it always serialize object to JSON. So I have fixed it like this in Program.cs:

    app.UseFastEndpoints(c =>
    {
        var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
    
        c.Serializer.ResponseSerializer = (rsp, dto, cType, jCtx, ct) =>
        {
            // this condition allows any content type
            if (dto is FileStreamResult fsr)
            {
                rsp.ContentType = fsr.ContentType;
                rsp.Body = fsr.FileStream;
    
                return rsp.SendStreamAsync(rsp.Body, fsr.FileDownloadName, null, fsr.ContentType); ;
            }
    
            rsp.ContentType = cType;
            return rsp.WriteAsync(JsonSerializer.Serialize(dto, options), ct);
        };
        c.Serializer.RequestDeserializer = async (req, tDto, jCtx, ct) =>
        {       
            using var reader = new StreamReader(req.Body);
            return JsonSerializer.Deserialize(await reader.ReadToEndAsync(), tDto, options);              
        };
    });
    

    Data

    public static async Task<FileStreamResult?> DownloadBlobAsync(string blobFileName , IConfiguration configuration)
        {
            string connectionString = configuration.GetSection("Azure").GetValue<string>("BlobConnectionString")!;
            string containerName = configuration.GetSection("Azure").GetValue<string>("BlobContainerName")!;
    
            BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
            BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
    
            BlobClient blobClient = containerClient.GetBlobClient(blobFileName);
    
            BlobDownloadInfo download = await blobClient.DownloadAsync();
            Stream stream = download.Content;
    
            var contentType = download.ContentType;
          
            return new FileStreamResult(stream, contentType)
            {
                FileDownloadName = blobFileName
            };
        }
    }
    

  2. I was able to test it with my below API application and download the blob content as a stream. Could you please check if that helps ?

    using Azure.Storage.Blobs;
    using System.IO;
    using System.Web.Mvc;
    using System.Web;
    using System.Threading.Tasks;
    using Azure.Storage.Blobs.Models;
    using System.Web.Http;
    using System.Net.Http;
    using System.Net;
    
    namespace StorageFileStream.Controllers
    {
        public class HomeController : ApiController
        {
    
            private readonly string _connectionString = "XXXXXXXXX";
            private readonly string _containerName = "MyContainerName";
            private readonly string _blobName = "MyBlobName";
    
           
            [System.Web.Http.HttpGet]
            public async Task<HttpResponseMessage> DownloadFile()
            {
                BlobServiceClient blobServiceClient = new BlobServiceClient(_connectionString);
                BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(_containerName);
                BlobClient blobClient = containerClient.GetBlobClient(_blobName);
    
               // Download the blob content
                BlobDownloadInfo download = await blobClient.DownloadAsync();
                Stream stream = download.Content;
    
               // Create an HttpResponseMessage
                HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
                response.Content = new StreamContent(stream);
               // Set the content type of the response to the blob's content type
                response.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(download.ContentType);
                response.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
                {
                    FileName = _blobName
                };
    
                // Set the content length header
                if (stream.CanSeek)
                {
                    response.Content.Headers.ContentLength = stream.Length;
                }
    
                return response;
            }
        }
    }
    
    

    I was able to invoke the above GET API operation and download the blob response stream. Hope this helps.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search