如何使用Blazor上传文件?

13

我发现了BlazorInputFile库,但是自2019年10月以来仍有未关闭的PR,我不确定这个库是否仍在维护。此外,在一些博客文章中,我也发现了关于如何在Blazor中使用JS上传文件的方法。虽然如果可能的话,我不想使用JS,但是我确实需要使用Blazor上传文件……那么,是否可以不使用JavaScript实现呢?


1
上传到哪里?如果您正在使用任何云服务,最好直接从客户端上传到某个存储服务。我使用Azure Storage和SAS令牌进行了这样的操作,这样我们就可以避免过载服务器的任何机会。 - Carlos Garcia
Blazor的性能改进只在使用http 1.1时发生,这是因为数据以块的形式传输且必须确认每个块,这会增加至少0.2秒的每个块的传输时间。块大小越大,改进越小。如果您使用的是http 1.0流模式,其中数据在一个块中获取,则没有理由使用Blazor,前提是您有足够的内存来处理数据。 - jdweng
我在我的Blazor WebAssembly项目中使用了https://blazorise.com/docs/components/file/,并且它对我来说运行良好。示例:https://stefh.github.io/RestEase-Client-Generator/ - Stef Heyenrath
5个回答

24

我曾尝试安装SteveSandersonMS的存储库,后来意识到,从2021年2月开始,在ASP.NET Core 5.0中实际上有一个本地的InputFile组件。

它支持在Blazor中上传单个和多个文件,并且易于使用(无需添加自己的JS文件等)。

我将其用于单个文件上传-您只需要在Razor页面中添加InputFile组件即可:

<InputFile OnChange="@SingleUpload" />

然后在我的情况下,我需要将该文件转换成字节数组:

@code {

   private async Task SingleUpload(InputFileChangeEventArgs e)
   {
       MemoryStream ms = new MemoryStream();
       await e.File.OpenReadStream().CopyToAsync(ms);
       var bytes = ms.ToArray();
       //do something with bytes
   }
}
InputFileChangeEventArgs提供了一个IReadOnlyList,其中包含IBrowserFile,您可以使用它来获取NameLastModifiedSizeContentType,以及一个用于获取StreamOpenReadStream方法。

ASP.NET文档中有关于如何获取多个文件的好的文档和代码。

您还需要添加System.IO命名空间:

@using System.IO

6
Microsoft的文章建议使用FileStream而不是MemoryStream。在Blazor Server中使用MemoryStream“可能会导致性能和安全问题”。请参考Microsoft的文章 - mfluehr

3

截至2020年6月,如果你正在使用表单(Form),最好的方法(WA)是使用Tewr's FileReader。让我们从API开始,Post控制器应该是:

      public async Task<IActionResult> PostMedia(
        [FromForm] IFormFile Picture,
        [FromForm] string Focus,
        [FromForm] string ID,
        [FromForm] string Title,
        [FromForm] string FormType,
        [FromForm] string AnimalType,
        [FromForm] string Mode,
        [FromForm] string AnimalID
        )
    {
        Debug.WriteLine($"-------------------------------------{Focus}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{ID}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{Title}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{FormType}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{AnimalType}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{Mode}-----------------------------------------------");
        Debug.WriteLine($"-------------------------------------{AnimalID}-----------------------------------------------");


        //check if file was fully uploaded
        if (Picture.Length == 0 || Picture == null)

            return BadRequest("Upload a new File");
        else
            return Ok ("do something with this data....") 
     }

那么客户端的 post 方法将是:

    public async Task PostFile()
  {
    //create content headers
    var content = new MultipartFormDataContent();
    content.Headers.ContentDisposition = new 
    System.Net.Http.Headers.ContentDispositionHeaderValue("form-data");

    //create content
    content.Add(new StreamContent(Pic.Stream, (int)Pic.Stream.Length), "Picture", Pic.FileName);
    content.Add(new StringContent(Pic.Title), "Title");
    content.Add(new StringContent(Pic.Focus), "Focus");
    content.Add(new StringContent(Pic.ID), "ID");
    content.Add(new StringContent(Pic.FormType), "FormType");
    content.Add(new StringContent(Pic.AnimalType), "AnimalType");
    content.Add(new StringContent(Pic.Mode), "Mode");
    content.Add(new StringContent(Pic.AnimalID), "AnimalID");
    //call to the server
    var upload = await Http.PostAsync("Media",content);

    //get server response
    Pic.Message = await upload.Content.ReadAsStringAsync();
   }

Tewr文件阅读器可以帮助您将文件读入流中,该流在我的情况下传递给Pic对象。绑定到表单中输入元素的onchange的读取函数如下:
  public async Task ReadFile()
   {
    var file = (await fileReaderService.CreateReference(Xelement).EnumerateFilesAsync()).FirstOrDefault();

    if (file == null)  return;


    var fileInfo = await file.ReadFileInfoAsync();

    Pic.FileName = fileInfo.Name;


    // Read into RAM
    using (var memoryStream = await file.CreateMemoryStreamAsync((int)fileInfo.Size))
    {
        // Copy store image into pic object
        Pic.Stream = new MemoryStream(memoryStream.ToArray());
    }

}

请注意,Xelement是ElementReference,它在表单中用作输入元素上的引用。

@Jonathan,我只在Web Assembly上尝试过它。我还没有尝试过服务器端的Blazor。可能有人尝试过,但我不确定。 - Xserge
在测试 .net Core 5 Blazor 服务器应用程序时,EnumerateFiles 函数没有返回任何文件。因此,在我的看法中,这种方法似乎不起作用。 - Nouman Qaiser

1

截至2020年4月2日的现状,您将需要JS,这是不可避免的。

您可以采取两种主要方法:

  • 在输入的onchange事件中获取文件数据,并通过传递byte[]调用C#方法 - 这基本上是您链接的文件选择器方法,其中您在Blazor应用程序中获取文件数据以进行任何想做的事情。

  • 在输入的onchange事件中获取文件数据,并使用JS调用远程端点来接收文件并对其进行处理(例如将其保存在NAS上或将其放入数据库中)。这是实际的文件上传,而不是文件选择器。

从编码角度来看,这两种方法相似 - 您需要JS。也许在Blazor的未来版本中,我们将获得一个<InputFile>,它将执行选择,因此您可以使用C# HTTP请求进行上传。

文件选择器方法相对容易实现(只需几行代码),但它不会为您提供服务器上的文件,您需要花费一些工作来获取它。文件上传方法更难以正确实现。我个人会使用别人的软件包来完成其中任意一个。对于文件上传,像Telerik UI for Blazor这样的商业软件可以胜任,而对于较简单的选择器,已经有其他链接示例的答案。顺便说一句,Telerik的演示文稿还有一个这样的示例,作为一些演示的组件实现。

1
I do this by using a component and some javascript (looks like a button). Once the component and js are incorporated, you never have to worry about it again...
这是上传组件(Upload.Razor):

@inject IJSRuntime JSRuntime

@if (AllowMulitple)
{
    <input id="Xinputfile00" type="file" accept="@Filter" @onchange="UploadFile" multiple hidden />
}
else
{
    <input id="Xinputfile00" type="file" accept="@Filter" @onchange="UploadFile" hidden />
}
<button class="btn btn-default" @onclick="ClickUpload">@Title</button>

@code {

    [Parameter]
    public FileData[] Files { get; set; }

    [Parameter]
    public string Filter { get; set; }

    [Parameter]
    public string Title { get; set; }

    [Parameter]
    public bool AllowMulitple { get; set; }

    [Parameter]
    public Action Uploaded { get; set; }

    async Task UploadFile()
    {
        string[] result = await JSRuntime.InvokeAsync<string[]>("blazorExtensions.GetFileData", "Xinputfile00");
        List<FileData> results = new List<FileData>();
        foreach (string file in result)
        {
            results.Add(new FileData(file));
        }
        this.Files = results.ToArray();
        if (Uploaded != null)
        {
            Uploaded();
        }
    }

    async Task ClickUpload()
    {
        await JSRuntime.InvokeVoidAsync("blazorExtensions.InvokeClick", "Xinputfile00");
    }

    public class FileData
    {
        public string Base64 { get; set; }
        public string MIMEType { get; set; }

        public byte[] Bytes
        {
            get
            {
                return Convert.FromBase64String(this.Base64);
            }
        }

        public FileData(string data)
        {
            if (string.IsNullOrWhiteSpace(data) || !data.Contains(","))
            {
                return;
            }
            string[] alldata = data.Split(',');
            this.MIMEType = alldata[0].Remove(0, 5).Replace(";base64", "");
            this.Base64 = alldata[1];
        }

    }

这里是JavaScript的摘录:

window.blazorExtensions = {

    GetFileData: async function (id) {
        var target = document.getElementById(id);
        var filesArray = Array.prototype.slice.call(target.files);
        return Promise.all(filesArray.map(window.blazorExtensions.fileToDataURL));
    },

    fileToDataURL: async function (file) {
        var reader = new FileReader();
        return new Promise(function (resolve, reject) {
            reader.onerror = function () {
                reader.abort();
                reject(new DOMException('Error occurred reading file ' + file));
            };
            reader.onload = function (event) {
                resolve(reader.result);
                console.log('resolved');
            };
            reader.readAsDataURL(file);
            console.log('returned');
        })
    },  

    InvokeClick: function (id) {
        var elem = document.getElementById(id);
        if (typeof elem.onclick == "function") {
            elem.onclick.apply(elem);
        }
        elem.click();
    },
}

And here's a calling markup sample:

<Upload @ref="upload" Filter=".xlsx" Title="Upload" AllowMulitple="false" Uploaded="DoMyExcelThingOrSomething" />

并调用上传后的方法:

    Upload upload;
    void DoMyExcelThingOrSomething()
{
    if (upload.Files.Length < 1 || string.IsNullOrWhiteSpace(upload.Files[0].Base64))
    {
        //...nothing good here...
        return;
    }
    //play with upload.Files here...
}

1
很棒的解决方案!有没有办法从代码中按名称加载文件? - Nys

0
对于 Blazor Server,以下代码将把文件上传到服务器。不需要单独的 API 服务器或使用 JS 代码。同时,它会将流转换为文件。
@using System.IO
@inject IWebHostEnvironment env

@*for ibrowser*@
@using Microsoft.AspNetCore.Components.Forms;

<h1>Blazor Server File Upload</h1>

<h3>@Message</h3>

<form @onsubmit="OnSubmit">
    <InputFile OnChange="OnInputFileChange" multiple />
    <br /><br />
    <button type="submit">Upload Selected File(s)</button>
</form>

@code {
    string Message = "No file(s) selected";
    IReadOnlyList<IBrowserFile> selectedFiles;

    void OnInputFileChange(InputFileChangeEventArgs e)
    {
        selectedFiles = e.GetMultipleFiles();
        Message = $"{selectedFiles.Count} file(s) selected";
        this.StateHasChanged();
    }

    async void OnSubmit()
    {
        foreach (var file in selectedFiles)
        {
            Stream stream = file.OpenReadStream();
            var path = $"{env.WebRootPath}\\{file.Name}";
            FileStream fs = File.Create(path);
            await stream.CopyToAsync(fs);
            stream.Close();
            fs.Close();
        }
        Message = $"{selectedFiles.Count} file(s)   uploaded on server";
        this.StateHasChanged();
    }
}

(对http://www.bipinjoshi.net/articles/06473cc7-a391-409e-948d-3752ba3b4a6c.aspx进行小修改)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接