【问题标题】:How do I upload files with Blazor?如何使用 Blazor 上传文件?
【发布时间】:2020-04-01 16:53:45
【问题描述】:

我找到了BlazorInputFile 库,但从 2019 年 10 月起仍有开放的 PR,我不确定这个库是否仍在维护。另外,我在博客中发现了几篇关于我们如何在 Blazor 中使用 JS 上传文件的文章。虽然如果可能的话我不想使用 JS,但我确实需要使用 Blazor 上传文件......是否可以不使用 JavaScript 这样做?

【问题讨论】:

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

标签: c# asp.net-core .net-core blazor


【解决方案1】:

我正在尝试安装 SteveSandersonMS' repo,然后意识到,截至 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 为您提供 IReadOnlyListIBrowserFile,您可以使用它来获取 NameLastModifiedSizeContentType,以及用于获取Stream.

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

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

@using System.IO

【讨论】:

  • Microsoft's article 说使用 FileStream 而不是 MemoryStream。使用 MemoryStream“可能会导致性能和安全问题,尤其是在 Blazor Server 中。”
【解决方案2】:

截至 2020 年 6 月,假设您使用表单,最好的方法 (WA) 是使用Tewr's FileReader。让我们从 API 开始,后控制器将是:

      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,在表单中作为输入元素的ref使用。

【讨论】:

  • @jonathan,仅在 Web 程序集上尝试过。我还没有尝试过服务器端 Blazor。有些可能,不确定。
  • 在 .net Core 5 blazer 服务器应用程序上进行测试时,EnumerateFiles 函数不会生成任何文件。所以这种方法在我看来似乎行不通。
【解决方案3】:

在目前的情况下(截至 2020 年 4 月 2 日),您将需要 JS,这是不可避免的。

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

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

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

从编码的角度来看,这两种方法是相似的——你需要 JS。也许在 Blazor 的未来版本中,我们将获得一个 &lt;InputFile&gt;,它将进行选择,以便您可以使用 C# HTTP 请求进行上传。

文件选择器方法相对容易实现(字面意思是几行),但它不会在服务器上给你一个文件,你必须为它工作一点。文件上传方法更难正确。我会亲自使用别人的包。对于文件上传,Telerik UI for Blazor 之类的东西可能适合商业用途,对于更简单的选择器,已经有另一个链接示例的答案。顺便说一句,Telerik 的演示也有一个这样的示例,例如为某些演示实现的组件。

【讨论】:

    【解决方案4】:

    我通过使用一个组件和一些 javascript(看起来像一个按钮)来做到这一点。组件和js一旦合并,就再也不用担心了……

    这是上传组件 (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();
        },
    }

    这是一个调用标记示例:

    &lt;Upload @ref="upload" Filter=".xlsx" Title="Upload" AllowMulitple="false" Uploaded="DoMyExcelThingOrSomething" /&gt;

    以及上传后调用的方法:

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

    【讨论】:

    • 很好的解决方案!有没有办法从代码中按名称加载文件?
    猜你喜欢
    • 2021-04-15
    • 1970-01-01
    • 1970-01-01
    • 2022-10-23
    • 2020-07-20
    • 1970-01-01
    • 1970-01-01
    • 2021-04-19
    • 2013-10-24
    相关资源
    最近更新 更多