使用 Web API 检索块中的文件数据以在浏览器中显示 (WIP)

Posted

技术标签:

【中文标题】使用 Web API 检索块中的文件数据以在浏览器中显示 (WIP)【英文标题】:Retrieving file data in chunks using Web API for display in browser (WIP) 【发布时间】:2019-04-28 16:25:27 【问题描述】:

我有这个工作,但我想分享一下,看看我是否遗漏了任何明显的东西,并解开为什么我的文件块大小必须是 2049 的倍数的谜团。主要要求是:

从网站上传的文件必须存储在 SQL Server 中,而不是作为文件 网站必须能够下载文件数据并将其显示为文件(在单独的窗口中打开。 网站是 angularjs/javascript SPA,没有服务器端代码,没有 MVC API 是 Web API 2(同样不是 MVC)

这里我只关注下载部分。基本上我正在做的是:

    从 SQL server varbinary 字段读取数据块 Web API 2 api 将文件名、mime 类型和字节数据作为 base64 字符串返回。注意 - 尝试返回字节数组,但 Web API 只是将其序列化为 base64 字符串。 连接块,将块转换为 blob 并显示

返回带有块的数据集的VB库函数(我必须使用这个处理数据库连接但不支持参数查询的库)

Public Function GetWebApplicationAttachment(ByVal intId As Integer, ByVal intChunkNumber As Integer, ByVal intChunkSize As Integer) As DataSet

    ' the starting number is NOT 0 based
    Dim intStart As Integer = 1
    If intChunkNumber > 1 Then intStart = ((intChunkNumber - 1) * intChunkSize) + 1
    Dim strQuery As String = ""
    strQuery += "SELECT FileName, "
    strQuery += "SUBSTRING(ByteData," & intStart.ToString & "," & intChunkSize.ToString & ") AS ByteData "
    strQuery += "FROM FileAttachments WHERE Id = " + intId.ToString + " "
    Try
        Return Query(strQuery)
    Catch ex As Exception
        ...
    End Try
End Function

从数据集创建文件对象的 Web API 业务规则位

...
    result.FileName = ds.Tables[0].Rows[0]["FileName"].ToString();
    // NOTE: Web API converts a byte array to base 64 string so the result is the same either way
    // the result of this is that the returned data will be about 30% bigger than the chunk size requested
    result.StringData = Convert.ToBase64String((byte[])ds.Tables[0].Rows[0]["ByteData"]);
    //result.ByteData = (byte[])ds.Tables[0].Rows[0]["ByteData"];
    ... some code to get the mime type
    result.MIMEType = ...

Web API 控制器(简化 - 删除所有安全和错误处理)

public IHttpActionResult GetFileAttachment([FromUri] int id, int chunkSize, int chunkNumber) 
    brs = new Files(...);
    fileResult file = brs.GetFileAttachment(appID, chunkNumber, chunkSize);
    return Ok(file);

angularjs 递归获取块并将它们放在一起的服务

    function getFileAttachment2(id, chunkSize, chunkNumber, def, fileData, mimeType) 
        var deferred = def || $q.defer();
        $http.get(webServicesPath + "api/files/get-file-attachment?id=" + id + "&chunkSize=" + chunkSize + "&chunkNumber=" + chunkNumber).then(
            function (response) 
                // when completed string data will be empty
                if (response.data.StringData === "") 
                    response.data.MIMEType = mimeType;
                    response.data.StringData = fileData;
                    deferred.resolve(response.data);
                 else 
                    if (chunkNumber === 1) 
                        // only the first chunk computes the mime type
                        mimeType = response.data.MIMEType;
                    
                    fileData += response.data.StringData;
                    chunkNumber += 1;
                    getFileAttachment2(appID, detailID, orgID, GUID, type, chunkSize, chunkNumber, deferred, fileData, mimeType);
                
            ,
            function (response) 
                ... error stuff
            
        );
        return deferred.promise;
    

进行调用的角度控制器方法。

    function viewFile(id) 
        sharedInfo.getWebPortalSetting("FileChunkSize").then(function (result) 
            // chunk size must be a multiple of 2049 ???
            var chunkSize = 0;
            if (result !== null) chunkSize = parseInt(result);
            fileHelper.getFileAttachment2(id, chunkSize, 1, null, "", "").then(function (result) 
                if (result.error === null) 
                    if (!fileHelper.viewAsFile(result.StringData, result.FileName, result.MIMEType)) 
                        ... error
                    
                    result = ;
                 else 
                    ... error;
                
            );
        );
    

最后是显示文件为下载的 javascript

    function viewAsFile(fileData, fileName, fileType) 
        try 
            fileData = window.atob(fileData);
            var ab = new ArrayBuffer(fileData.length);
            var ia = new Uint8Array(ab);    // ia provides window into array buffer
            for (var i = 0; i < fileData.length; i++) 
                ia[i] = fileData.charCodeAt(i);
            
            var file = new Blob([ab],  type: fileType );
            fileData = "";
            if (window.navigator.msSaveOrOpenBlob) // IE10+
                window.navigator.msSaveOrOpenBlob(file, fileName);
            else  // Others
                var a = document.createElement("a"),
                    url = URL.createObjectURL(file);
                a.href = url;
                a.download = fileName;
                document.body.appendChild(a);
                a.click();
                setTimeout(function () 
                    document.body.removeChild(a);
                    window.URL.revokeObjectURL(url);
                , 0);
            
            return true;
         catch (e) 
            ... error stuff
        
    

我已经看到一种更 RESTful 的方法是使用标头来指示块范围并将文件元数据与文件块分开。我也可以尝试返回数据流而不是 Base64 编码的字符串。如果有人有这方面的建议,请告诉我。

【问题讨论】:

我已经看出这是错误的做法。应该删除吗? 【参考方案1】:

这完全是错误的做法。万一这对我有帮助,这就是我最终要做的。

动态创建锚标记的 href 地址以返回文件(查询字符串中的安全令牌和参数) 从数据库中获取字节数组 web api 调用返回响应消息(见下方代码)

这更快、更可靠,但提供的进度监控方式更少。

业务规则方法使用...

...
file.ByteData = (byte[])ds.Tables[0].Rows[0]["ByteData"];
...

web api 控制器

public HttpResponseMessage ViewFileAttachment([FromUri] int id, string token) 
    HttpResponseMessage response = new HttpResponseMessage();
    ... security stuff
    fileInfoClass file = ... code to get file info
    response.Content = new ByteArrayContent(file.ByteData);
    response.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment") 
            FileName = file.FileName
        ;
    response.Content.Headers.ContentType = new MediaTypeHeaderValue(file.MIMEType);
    return response;

这甚至可以通过流媒体来改善

【讨论】:

以上是关于使用 Web API 检索块中的文件数据以在浏览器中显示 (WIP)的主要内容,如果未能解决你的问题,请参考以下文章

如何计算每个目录中的文件数?

使用聚合查找最新批次中的文件数

计算Linux目录中的文件数? [关闭]

查找目录中的文件数

如何使用 gsutil 计算存储桶文件夹中的文件数

如何使用 python 以编程方式计算存档中的文件数