如何使用 Blazor 生成和保存文件客户端?

Posted

技术标签:

【中文标题】如何使用 Blazor 生成和保存文件客户端?【英文标题】:How can one generate and save a file client side using Blazor? 【发布时间】:2018-10-06 22:01:26 【问题描述】:

我想要一个 SPA 来完成客户端的所有工作,甚至生成一些图形/视觉效果。

我希望能够让用户单击按钮并保存页面中的视觉效果、表格和其他内容(可见和不可见,因此右键单击保存或复制/粘贴并不总是选项)。

如何从 webassembly/blazor 库中调用函数,获取结果并将其保存为客户端的文件?

这个想法是这样的......?

cshtml

<input type="file" onchange="@ReadFile">

<input type="file" onchange="@SaveFile">

@functions
object blazorObject = new blazorLibrary.SomeObject();

void ReadFile()
    blazorObject.someFunction(...selectedFile?...);


void SaveFile()
    saveFile(...selectedFile..?)



【问题讨论】:

保存文件时,您的意思是告诉浏览器“下载/另存为”您生成的文件吗? 是的。这就是我的意思。 我不太清楚现在用blazor能做多少,但是保存文件的JS版本可以看here 我认为这是保存文件的有用解决方法(如果目前无法在 Blazor 中完成),但我遇到的困难部分(即使我走 JS 路线)是生成信息客户端使用 Blazor 并将其放入某个文件以供下载。 byte as base64 string 与二进制文件不同。 【参考方案1】:

Blazor 的创建者 Steve Sanderson 在他最近的一次演示中使用 javascript 互操作来完成类似的任务。

您可以在 BlazorExcelSpreadsheet 上找到示例

解决方案包括三个部分:

1) JavaScript

function saveAsFile(filename, bytesBase64) 
        var link = document.createElement('a');
        link.download = filename;
        link.href = "data:application/octet-stream;base64," + bytesBase64;
        document.body.appendChild(link); // Needed for Firefox
        link.click();
        document.body.removeChild(link);
    

2) C# 互操作包装器

public static class FileUtil

    public async static Task SaveAs(IJSRuntime js, string filename, byte[] data)
    
        await js.InvokeAsync<object>(
            "saveAsFile",
            filename,
            Convert.ToBase64String(data));
                

3) 从您的组件调用

@inject IJSRuntime js
@functions 
    void DownloadFile() 
        var text = "Hello, world!";
        var bytes = System.Text.Encoding.UTF8.GetBytes(text);
        FileUtil.SaveAs(js, "HelloWorld.txt", bytes);
    

你可以在Blazor Fiddle看到它的一个动作

【讨论】:

还有没有别的办法? 据我所知,这仍然是实际的解决方案。 即使是使用JS,我相信这是最好的方式,因为它是纯Blazor。混合 Blazor 和 MVC 并不是很好。 如果下载一个相当大的文件,这个解决方案不会杀死内存吗?转换为 base64 对我来说似乎是一个表演障碍。 Blazor 是否存在 GitHub 问题,支持此开箱即用?谢谢!【参考方案2】:
    添加链接

&lt;a class="form-control btn btn-primary" href="/download?name=test.txt" target="_blank"&gt;Download&lt;/a&gt;
    添加带有路由的 Razor 页面 2.1。创建 Razor 页面“Download.cshtml”或其他名称...“PewPew.cshtml”...无所谓 2.2.将下一个代码放入创建的页面中@page "/download"@model MyNamespace.DownloadModel 编辑Download.cshtml.cs文件
public class DownloadModel : PageModel

    public IActionResult OnGet(string name) 
        // do your magic here
        var content = new byte[]  1, 2, 3 ;
        return File(content, "application/octet-stream", name);
    

【讨论】:

唉,这对我不起作用。我得到Sorry, there's nothing at this address.。 ? @JohnyL,我已经更新了帖子。请尝试使用步骤 2.1 和 2.2。可能,您需要编辑路线 @JohnyL 您是否将新文件的构建操作更改为内容?默认为无,您将收到此错误。我花了几个小时才发现这是问题所在。 @model 仅适用于 MVC 视图和 Razor 页面(不是 Blazor 页面)。见doc'n【参考方案3】:

我创建了一个存储库和 nuget 包来解决和简化这个问题,请看一下:https://github.com/arivera12/BlazorDownloadFile

【讨论】:

文档很糟糕,也许你可以在你的答案中添加一个例子。 我知道文档不是最好的,但我认为它并不差。方法在那里,源代码记录在案。我还有一个示例项目,您可以在 repo 中查看。 @revobtz,如果我必须查看源代码以了解方法的作用,那么您的文档不仅“差”,而且不存在。摘要不是“文档”,它们是程序员不被视为美化代码猴子的最低要求。但是,只是“复制粘贴的方法或参数名称”的摘要并不是摘要,它们很烦人。告诉我参数“超时”是“超时”是在告诉我“我不应该被允许在距离计算机 100 英尺的范围内”。 @Tessaract 就是这样,如果你想改进 repo 文档,你可以来贡献,我现在正在为 net 6 做一个更新的版本来修复一些现有的错误,在在不久的将来,我将改进文档。多年来我一直忙于工作,很抱歉让您和其他所有人在文档方面失望。【参考方案4】:

Eugene 提出的解决方案工作,但有一个缺点。如果您尝试对大文件执行此操作,则浏览器在将 blob 下载到客户端时会挂起。我发现的解决方案是稍微更改该代码并将文件存储在临时目录中,并让服务器使用其机制来提供文件,而不是将其作为 blob 推送。

在服务器配置中添加:

app.UseStaticFiles();
app.UseStaticFiles(new StaticFileOptions

    FileProvider = new PhysicalFileProvider(
        Path.Combine(___someTempDirectoryLocation___, "downloads")),
    RequestPath = "/downloads"
);

这会将静态链接添加到系统某处的下载文件夹。将您想要下载的任何文件放在那里。

接下来您可以使用 http://pathToYourApplication/downloads/yourFileName 使用指向该文件的链接或使用主要示例中的简化保存 javascript:

function saveAsFile(filename) 
        var link = document.createElement('a');
        link.download = filename;
        link.href = "/downloads/" + filename;
        document.body.appendChild(link); // Needed for Firefox
        link.click();
        document.body.removeChild(link);
    

它将为您将其推送到用户浏览器。

【讨论】:

这只是在公共端点上提供文件。尝试动态生成内容时没有用。 @StijnVanAntwerpen 不是真的,你把文件保存到静态文件后,你用他提供的javascript让浏览器下载。 @StijnVanAntwerpen 它适用于生成的内容。您创建一个生成内容的操作,将其写入示例代码前半部分中配置的预定义目录中的文件。完成后,您使用“saveAsFile”代码将其推送到客户端,该代码使用 javascript 启动下载过程。我将它用于生成的文件内容,我知道它有效。【参考方案5】:

我是这样做的:

将新的 DownloadController.cs 添加到名为 Controllers 的文件夹中

[Controller, Microsoft.AspNetCore.Mvc.Route("/[controller]/[action]")]
public class DownloadController : Controller

    private readonly IDataCombinerService DataCombinerService;
    private readonly IDataLocatorService DataLocatorService;

    public DownloadController(IDataCombinerService dataCombinerService, IDataLocatorService dataLocatorService)
    
        DataCombinerService = dataCombinerService;
        DataLocatorService = dataLocatorService;

    

    [HttpGet]
    [ActionName("Accounts")]
    public async Task<IActionResult> Accounts()
    
        var cts = new CancellationTokenSource();
        var Accounts = await DataCombinerService.CombineAccounts(await DataLocatorService.GetDataLocationsAsync(cts.Token), cts.Token);

        var json = JsonSerializer.SerializeToUtf8Bytes(Accounts, Accounts.GetType(), new JsonSerializerOptions(JsonSerializerDefaults.Web)  WriteIndented = true );
        var stream = new MemoryStream(json);

        var fResult = new FileStreamResult(stream, MediaTypeNames.Application.Json)
        
            FileDownloadName = $"Account Export DateTime.Now.ToString("yyyyMMdd").json"
        ;

        return fResult;
    

    [HttpGet]
    public IActionResult Index()
    
        return View();
    

严格来说,这里不需要异步,因为它不需要处理任何其他内容,但该方法用于在屏幕上显示相同的结果。

然后在 Startup.cs 中

app.UseEndpoints(endpoints =>

添加:

endpoints.MapControllerRoute(
    name: "default",
    defaults: new  action = "Index" ,
    pattern: "controller/action");

    endpoints.MapControllers();

同样,严格来说,默认值不是必需的,它是一个标准的 MVC 控制器。

这就像一个经典的 MVC 响应一样,因此您可以从您喜欢的任何来源发回任何文件。使用中间件服务在视图和下载器控制器之间保存临时数据可能会有所帮助,以便客户端下载相同的数据。

【讨论】:

如何在客户端下载文件?它来自服务器,这是一种允许您将文件下载到客户端的解决方案。我确实做了与接受的答案相同的事情,只是方式不同。【参考方案6】:

由于某种原因,Eugene 的回答对我不起作用,但现在有 official documentation 关于如何做到这一点,这非常相似并且在我的 Blazor Server 应用程序中运行良好。

将这些 JavaScript 方法添加到您的 _Host.cshtml 文件中:

<script type="text/javascript">
    async function downloadFileFromStream(fileName, contentStreamReference) 
        const arrayBuffer = await contentStreamReference.arrayBuffer();
        const blob = new Blob([arrayBuffer]);
        const url = URL.createObjectURL(blob);
        triggerFileDownload(fileName, url);
        URL.revokeObjectURL(url);
    

    function triggerFileDownload(fileName, url) 
        const anchorElement = document.createElement('a');
        anchorElement.href = url;

        if (fileName) 
            anchorElement.download = fileName;
        

        anchorElement.click();
        anchorElement.remove();
    
</script>

在您的 .razor 页面文件中,添加:

@using System.IO
@inject IJSRuntime JS

<button @onclick="DownloadFileFromStream">
    Download File From Stream
</button>

@code 
    private Stream GetFileStream()
    
        var randomBinaryData = new byte[50 * 1024];
        var fileStream = new MemoryStream(randomBinaryData);
        return fileStream;
    
    
    private async Task DownloadFileFromStream()
    
        var fileStream = GetFileStream();
        var fileName = "log.bin";
        using var streamRef = new DotNetStreamReference(stream: fileStream);
        await JS.InvokeVoidAsync("downloadFileFromStream", fileName, streamRef);
    

【讨论】:

这不是客户端解决方案。请重新阅读问题。

以上是关于如何使用 Blazor 生成和保存文件客户端?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 blazor 创建 cookie 客户端

Blazor:尝试保存项目时检测到对象循环

Blazor Server 如何跨多个选项卡和刷新保存数据

Blazor之ABC

使用 Blazor 访问设备摄像头

是否可以使用客户端生成的 blob url 保存到 Google Drive