问题描述
我已经在My Server-API中创建了一个HttpGet
,它创建了一个CSV文件,并用FileStreamResult
:返回它
[HttpGet]
public IActionResult Get() {
// do logic to create csv in memoryStream
return new FileStreamResult(memoryStream, "text/csv;charset=utf-8") {
FileDownloadName = "products.csv",
};
}
在我的Blazor客户端应用程序中,我创建了一个带有处理程序的按钮:
private async Task DownloadCatalog() {
var file = HttpClient.GetAsync("api/csvProduct");
// ... how do I download the file in the browser?
}
调用了控制器中的Get,但我不知道如何使该文件在API调用后下载到浏览器中。
推荐答案
浏览器不允许脚本写入文件系统,无论是用JavaScript还是WebAssembly编写的。仅当用户单击链接时,浏览器才会显示下载对话框。
使用链接按钮
如果最终文件是直接从服务器返回的,最简单的解决方案是使用一个链接按钮,该按钮带有指向API端点的URL(可能在运行时计算)。您可以使用download
属性指定文件名。当用户单击该链接时,将使用download
名称
例如:
<a id="exportCsv" class="btn" href="api/csvProduct" download="MyFile.csv"
role="button" target="=_top">Export to CSV</a>
或
@if (_exportUrl != null)
{
<a id="exportCsv" class="btn" href="@_exportUrl" download="MyFile.csv"
role="button" target="=_top">Export to Csv</a>
}
...
int _productId=0;
string? _exportUrl=null;
async Task Search()
{
//Get and display a product summary
_model=await GetProductSummary(_productId);
//Activate the download URL
_exportUrl = $"api/csvProduct/{_productId}";
}
使用动态生成的数据链接
如果这是不可能的,您必须使用数据URL或Blob在JavaScript中创建一个link元素,然后单击它。这很慢,有三个原因:- 您正在制作下载文件的内存副本,该副本至少比原始文件大33%。
- JS互操作数据封送慢,这意味着将字节从Blazor传递到Javascript也很慢。
- 字节数组作为base64字符串传递。需要将它们解码回字节数组以用作Blob。
文章Generating and efficiently exporting a file in a Blazor WebAssembly application说明如何使用Blazor运行时技巧传递字节而不使用封送处理。
如果使用Blazor WASM,可以使用InvokeUnmarshalled
传递byte[]
数组,并使其在JavaScript中显示为Uint8Array
。
byte[] file = Enumerable.Range(0, 100).Cast<byte>().ToArray();
string fileName = "file.bin";
string contentType = "application/octet-stream";
// Check if the IJSRuntime is the WebAssembly implementation of the JSRuntime
if (JSRuntime is IJSUnmarshalledRuntime webAssemblyJSRuntime)
{
webAssemblyJSRuntime.InvokeUnmarshalled<string, string, byte[], bool>("BlazorDownloadFileFast", fileName, contentType, file);
}
else
{
// Fall back to the slow method if not in WebAssembly
await JSRuntime.InvokeVoidAsync("BlazorDownloadFile", fileName, contentType, file);
}
BlazorDownloadFileFast
JavaScript方法检索数组,将其转换为文件,然后通过URL.createObjectURL
转换为可以单击的安全数据URL:
function BlazorDownloadFileFast(name, contentType, content) {
// Convert the parameters to actual JS types
const nameStr = BINDING.conv_string(name);
const contentTypeStr = BINDING.conv_string(contentType);
const contentArray = Blazor.platform.toUint8Array(content);
// Create the URL
const file = new File([contentArray], nameStr, { type: contentTypeStr });
const exportUrl = URL.createObjectURL(file);
// Create the <a> element and click on it
const a = document.createElement("a");
document.body.appendChild(a);
a.href = exportUrl;
a.download = nameStr;
a.target = "_self";
a.click();
// We don't need to keep the url, let's release the memory
// On Safari it seems you need to comment this line... (please let me know if you know why)
URL.revokeObjectURL(exportUrl);
a.remove();
}
使用Blazor Server,封送处理是不可避免的。在本例中,调用速度较慢的BlazorDownloadFile
方法。byte[]
数组被封送为Base64字符串,必须对其进行解码。不幸的是,JavaScript的atob
和btoa
函数can't handle every value,因此我们需要另一种方法将base64解码为Uint8Array:
function BlazorDownloadFile(filename, contentType, content) {
// Blazor marshall byte[] to a base64 string, so we first need to convert the string (content) to a Uint8Array to create the File
const data = base64DecToArr(content);
// Create the URL
const file = new File([data], filename, { type: contentType });
const exportUrl = URL.createObjectURL(file);
// Create the <a> element and click on it
const a = document.createElement("a");
document.body.appendChild(a);
a.href = exportUrl;
a.download = filename;
a.target = "_self";
a.click();
// We don't need to keep the url, let's release the memory
// On Safari it seems you need to comment this line... (please let me know if you know why)
URL.revokeObjectURL(exportUrl);
a.remove();
}
和解码器函数,借用自Mozilla的Base64 documentation
// Convert a base64 string to a Uint8Array. This is needed to create a blob object from the base64 string.
// The code comes from: https://developer.mozilla.org/fr/docs/Web/API/WindowBase64/D%C3%A9coder_encoder_en_base64
function b64ToUint6(nChr) {
return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 ? nChr - 71 : nChr > 47 && nChr < 58 ? nChr + 4 : nChr === 43 ? 62 : nChr === 47 ? 63 : 0;
}
function base64DecToArr(sBase64, nBlocksSize) {
var
sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, ""),
nInLen = sB64Enc.length,
nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2,
taBytes = new Uint8Array(nOutLen);
for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
}
nUint24 = 0;
}
}
return taBytes;
}
Blazor 6
最近发布的ASP.NET Core 6 Preview 6不再将byte[]
封送为base64字符串。应该可以使用以下函数
function BlazorDownloadFile(filename, contentType, data) {
// Create the URL
const file = new File([data], filename, { type: contentType });
const exportUrl = URL.createObjectURL(file);
// Create the <a> element and click on it
const a = document.createElement("a");
document.body.appendChild(a);
a.href = exportUrl;
a.download = filename;
a.target = "_self";
a.click();
// We don't need to keep the url, let's release the memory
// On Safari it seems you need to comment this line... (please let me know if you know why)
URL.revokeObjectURL(exportUrl);
a.remove();
}
这篇关于使用Blazor应用程序从服务器下载文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!