57

我想要一个 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..?)
}

}
4

6 回答 6

75

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中看到它的一个动作

于 2018-12-17T20:27:55.393 回答
18
  1. 添加链接

<a class="form-control btn btn-primary" href="/download?name=test.txt" target="_blank">Download</a>

  1. 添加带有路由
    2.1 的 Razor 页面。创建 Razor 页面“ Download.cshtml ”或其他名称...“ PewPew.cshtml ”...无关紧要
    2.2。将下一个代码放在创建的页面中
    @page "/download"
    @model MyNamespace.DownloadModel
  2. 编辑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);
    }
}
于 2020-01-06T15:03:36.193 回答
8

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

于 2020-04-20T09:28:04.333 回答
3

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);
    }

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

于 2020-09-28T09:48:45.277 回答
0

我是这样做的:

将新的 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 响应一样起作用,因此您可以从您喜欢的任何来源发回任何文件。使用中间件服务在视图和下载器控制器之间保存临时数据可能会有所帮助,以便客户端下载相同的数据。

于 2021-07-21T15:00:47.157 回答
-1

由于某种原因,尤金的回答对我不起作用,但现在有关于如何做到这一点的官方文档,这非常相似并且在我的 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);
    }
}
于 2022-01-05T14:04:08.917 回答