学习ASP.NET Core Blazor编程系列十七——文件上传(上)

时间:2022-12-18 18:05:37
 

      从本篇文章开始我们来讲在图书租赁系统中如何使用内置的文件上传组件进行文件上传功能的开发。本文的示例适合上传小型文件。本篇文章演示如何通过Blazor的内置组件InputFile将文件上传至服务器。

     安全注意事项

  在向用户提供向上传文件的功能时,必须格外注意安全性。攻击者可能对系统执行拒绝服务和其他攻击。所以在提供上传功能时需要注意以下安全措施:

    1. 将文件上传到系统上的专用文件上传目录,这样可以更轻松地对上传内容实施安全措施。如果允许文件上传,请确保在上传目录禁用执行权限。

    2. 上传文件的文件名在服务器端保存时要由应用程序自动重新命名文件名称,而不是采用用户输入或已上传文件的文件名。

    3.请不要将上传的文件保存在与应用程序相同的目录下。

    4. 仅允许使用一组特定的文件扩展名。

    5. 在服务端重新执行客户端检查。 不要相信客户端检查,因为客户端检查很容易规避。

    6. 检查上传文件大小,防止上传文件的大小比预期的文件大小大。

    7. 对上传文件的内容进行病毒/恶意软件扫描程序。

    8.应用程序中的文件不能被具有相同名称的上传文件覆盖。

警告

   将恶意代码上传到系统通常是执行代码的第一步,这些代码可以实现以下功能:

   1. 完全接管系统。

   2. 重载系统,导致系统完全崩溃。

   3. 泄露用户或系统数据。

 

为避免处理上传文件文件时出现重复代码,我们首先创建一个静态类用于处理上传功能。

   1.在Visual Studio 2022 的解决方案资源管理器中创建一个“Utils”文件夹。

   2.在Visual Studio 2022的解决方案资源管理器中,鼠标左键选中“Utils”文件夹,右键单击,在弹出菜单中选择“添加—>类”(如下图)。 将类命名为“FileHelpers”。

 学习ASP.NET Core Blazor编程系列十七——文件上传(上)

   3.在Visual Studio 2022的文本编辑器中打开我们刚才创建的“FileHelpers.cs”类文件,并添加以下内容。其中方法 ProcessFormFile 接受 IBrowserFile 和 ModelStateDictionary等参数,保存成功则空字符串,否则返回错误信息。 检查内容类型和长度。 如果上传文件未通过校验,将向 ModelState 添加一个错误。

using BlazorAppDemo.Models;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.ComponentModel.DataAnnotations;
using System.Net;
using System.Reflection;
using System.Text;
 
namespace BlazorAppDemo.Utils
{

    public class FileHelpers
    {

        public static async Task<string> ProcessFormFile(IBrowserFile formFile, ModelStateDictionary modelState,IWebHostEnvironment envir,int maxFileSize)
        {
            var fieldDisplayName = string.Empty;        
 
            if (!string.IsNullOrEmpty(formFile.Name))
            {
                // 如果名称没有找到,将会有一个简单的错误消息,但不会显示文件名称
                string displayFileName = formFile.Name.Substring(formFile.Name.IndexOf(".") + 1);

                fieldDisplayName = $"{displayFileName} ";
               
            }
 
            //使用path.GetFileName获取一个带路径的全文件名。
            //通过HtmlEncode进行编码的结果必须在错误消息中返回。
            var fileName = WebUtility.HtmlEncode(Path.GetFileName(formFile.Name));
 
            if (formFile.ContentType.ToLower() != "text/plain")
            {
                modelState.AddModelError(formFile.Name,
                                         $"The {fieldDisplayName}file ({fileName}) must be a text file.");
            }
 
 
            //校验文件长度,如果文件不包含内容,则不必读取文件长度。
            //此校验不会检查仅具有BOM(字节顺序标记)作为内容的文件,
            //因此在读取文件内容后再次检验文件内容长度,以校验仅包含BOM的文件。
            if (formFile.Size == 0)
            {
                modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) is empty.");

            }
            else if (formFile.Size > maxFileSize)
            {

                modelState.AddModelError(formFile.Name, $"The {fieldDisplayName}file ({fileName}) exceeds 1 MB.");
            }
            else
            {
                try
                {
                  

                    //获取一个随机文件名
                    var trustedFileNameForFileStorage=Path.GetRandomFileName();
                    var path = Path.Combine(envir.ContentRootPath, envir.EnvironmentName, "unsafeUploads", trustedFileNameForFileStorage);
 
                    using (
                        var reader =
                            new FileStream(
                                path,
                                FileMode.Create))
                    {
                        await formFile.OpenReadStream(maxFileSize).CopyToAsync(reader);
                    }

                }
                catch (Exception ex)
                {
                    modelState.AddModelError(formFile.Name,

                                             $"The {fieldDisplayName}file ({fileName}) upload failed. " +

                                             $"Please contact the Help Desk for support. Error: {ex.Message}");
             //return ex.Message;
               throw ex;
                }
            }
            return string.Empty;
        }

    }

}

 

 

二、添加前端代码

1.  在Visual Studio 2022的解决方案资源管理器中,鼠标右键单击“Pages”文件夹。在弹出菜单中选择,添加-->Razor组件。如下图。

 学习ASP.NET Core Blazor编程系列十七——文件上传(上)

2.在弹出对话框,名称中输入FileUpload1.razor。如下图。

学习ASP.NET Core Blazor编程系列十七——文件上传(上)

3. 在Visual Studio 2022的解决方案资源管理器中,鼠标左键双击“Pages\FileUpload1.razor”文件,在文本编辑器中打开,在文件的顶部添加@page指令。并添加如下代码。

@page "/FileUpload1"
@using BlazorAppDemo.Utils
@using Microsoft.AspNetCore.Mvc.ModelBinding
@inject IWebHostEnvironment Environ
<h3>多文件上传示例</h3>
<p>
    <label>
        提示信息:@Message
 
    </label>
</p>
<p>
    <label>
        上传文件最大可以为:<input type="number" @bind="maxFileSize"/>字节
 
    </label>
</p>
<p>
    <label>
        一次可上传:<input type="number" @bind="maxAllowedFiles" />个文件
 
    </label>
</p>
<p>
    <label>
        选择上传文件:<InputFile OnChange="@LoadFiles" multiple />
 
    </label>
</p>
@if (isLoading)
{
    <p>文件上传中......</p>
}
else
{
    <ul>
        @foreach (var file in loadedFiles)
        {
            <li>
                <ul>
                    <li>文件名:@file.Name</li>
                    <li>最后修改时间:@file.LastModified.ToString()</li>
                    <li>文件大小(byte):@file.Size</li>
                    <li>文件类型:@file.ContentType</li>
                </ul>
            </li>
           
        }
 
    </ul>
}
 
@code {
    private List<IBrowserFile> loadedFiles = new();
    private long maxFileSize = 1024 * 18;
    private int maxAllowedFiles = 2;
    private bool isLoading;
    private string Message = string.Empty;
 
    private async Task LoadFiles(InputFileChangeEventArgs e)
    {
        isLoading = true;
        loadedFiles.Clear();
        foreach (var file in e.GetMultipleFiles(maxAllowedFiles))
        {
            try
            {
                ModelStateDictionary modelState = new ModelStateDictionary();
                loadedFiles.Add(file);
                string result=  await FileHelpers.ProcessFormFile(file, modelState, Environ, maxFileSize);

                if (string.IsNullOrEmpty(result))
                {
                    Message = "上传成功!";
                }else
                Message = "上传失败!";
            }
            catch (Exception ex)
            {
                Message = ex.Message;
               
            }
        }
        isLoading = false;
    }
}

 

 


4. 在Visual Studio 2022的解决方案资源管理器中,鼠标左键双击“Shared\NavMenu.razor”文件,在文本编辑器中打开,我们在此文中添加指向上传文件的菜单。具体代码如下:

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">BlazorAppDemo</a>

        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>
 
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="BookIndex">
                <span class="oi oi-list-rich" aria-hidden="true"></span> 图书列表
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="AddBook">
                <span class="oi oi-list-rich" aria-hidden="true"></span> 添加图书
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="FileUpload1">
                <span class="oi oi-list-rich" aria-hidden="true"></span> 上传文件
            </NavLink>
        </div>
    </nav>
</div>

 
@code {
   private bool collapseNavMenu = true;

     private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {

        collapseNavMenu = !collapseNavMenu;

    }

} 

     5. 在Visual Studio 2022的菜单栏上,找到“调试à开始调试”或是按F5键,Visual Studio 2022会生成BlazorAppDemo应用程序,并在浏览器中打开Home页面,我们使用鼠标点击左边的菜单栏上的“上传文件”菜单项,页面会进入“FileUpload1”页面,我们会看到我们写的图书列表页面,如下图。

 学习ASP.NET Core Blazor编程系列十七——文件上传(上)

6. 我们在“多文件上传示例”中选择一个上传文件,然后应用程序会自动上传文件,但是却会提示错误,错误信息如下图中1处,指明“找不到路径的一部分”。我们打开资源管理器,在项目中找一下图中2处的目录,发现没有这样的目录结构。我们手动创建一下即可。 学习ASP.NET Core Blazor编程系列十七——文件上传(上)

 

7. 我们在“多文件上传示例”中选择一个上传文件,然后应用程序会自动上传文件,上传到到目录中却不是我们选择的文件名,是一个随机的文件名。如下图。

 学习ASP.NET Core Blazor编程系列十七——文件上传(上)