Enterprise-level component library based on Bootstrap and Blazor

star nuget license download repo commit master coverage

Upload

Upload the file by clicking

The InputUpload component is used with other form components to display the file name, select the file and upload it by clicking the browse button, and by setting the ShowRemoveButton parameter, display the delete button, click the delete button to call back onDelete delegate method

Demo

Use the file upload component to constrain the file format within the form

Demo
  • Using the ValidateForm form component, custom validation is set by setting the fileValidation label of the model properties to support file extension size validation, in this case with the extension .png .jpg .jpeg and the file size limit to 50K
  • After selecting the file and not starting to upload the file, click the submit button to verify that the data is legitimate, and then upload the file OnSubmit callback delegate, noting that the Picture property type is IBrowserFile

The ButtonUpload components, classic styles, user click button to pop up the file selection box.

Demo

Click on the browse button select file upload, in this case set IsMultiple-true multiple-selectable file can be uploaded

When you set up IsSingle , you can upload only one image or file

Use DefaultFileList to set up uploaded content

Demo
Test.xls (0 B)
Test.doc (0 B)
Test.ppt (0 B)
Test.mp3 (0 B)
Test.mp4 (0 B)
Test.pdf (0 B)
Test.cs (0 B)
Test.zip (0 B)
Test.txt (0 B)
Test.dat (0 B)

Use DefaultFileList to set up uploaded content

Demo

AvatarUpload component, using the OnChange to limit the format and size of images uploaded by users. In this example, only jpg/png/bmp/jpeg/gif five picture formats are allowed

Demo

Card form avatar box

Round avatar frame

When you set up IsSingle , you can upload only one image or file

RELATED: [Accept] < a href='http://www.iana.org/assignments/media-types/media-types.xhtml' target='_blank' >[Media Types]

Set the preview address PrevUrl with the DefaultFileList property

Verify that an example of using a picture box is used in the form

CardUpload components and rendered in card-style band preview mode

Demo

SSR mode
Server Side mode, you can use the IWebHostEnvironment injection service to get to the wwwroot directory and save the file to the images/uploader , which does not require a direct call the controller secondary of MVC SaveToFile method
Wasm mode
It wasn't available in wasm mode, IWebHostEnvironment needed to save files to the server side by calling webapi interface, and so on
Interested students can view their knowledge of Upload components through the wiki in the open source repository related resources of the [The portal]
In this example, server-side verification prompts the file for too much prompt when the file size exceeds 200MB

In this example, the ShowProgress=true display upload progress bar

When you set up IsSingle , you can upload only one image or file

Icons are displayed in different file formats

Demo
Test.xls (0 B)
Test.doc (0 B)
Test.ppt (0 B)
Test.mp3 (0 B)
Test.mp4 (0 B)
Test.pdf (0 B)
Test.cs (0 B)
Test.zip (0 B)
Test.txt (0 B)
Test.dat (0 B)

InputUpload

Loading...

ButtonUpload/CardUpload

Loading...

AvatarUpload

Loading...
@page "/uploads"

<h3>Upload</h3>

<h4>Upload the file by clicking</h4>

<DemoBlock Title="Basic usage" Introduction="The <code> InputUpload </code> component is used with other form components to display the file name, select the file and upload it by clicking the <b>browse button</b>, and by setting the <code>ShowRemoveButton </code> parameter, display the <b> delete button</b>, click the delete button to call back <code> onDelete </code> delegate method" Name="Normal">
    <div class="row g-3">
        <div class="col-12 col-sm-6">
            <label for="text1" class="form-label">Name:</label>
            <input id="text1" class="form-control">
        </div>
        <div class="col-12 col-sm-6">
            <label for="text2" class="form-label">Adress:</label>
            <input id="text2" class="form-control">
        </div>
        <div class="col-12">
            <label for="text3" class="form-label">Photo:</label>
            <InputUpload TValue="string" Id="text3" ShowDeleteButton="true" OnChange="@OnFileChange" OnDelete="@OnFileDelete"></InputUpload>
        </div>
    </div>
    <BlockLogger @ref="Trace" class="mt-3" />
</DemoBlock>

<DemoBlock Title="FormSettings" Introduction="Use the file upload component to constrain the file format within the form" Name="FormSettings">
    <ul class="ul-demo">
        <li>Using the <code>ValidateForm </code> form component, custom validation is set by setting the <code>fileValidation </code> label of the model properties to support file <b> extension </b> <b> size </b> validation, in this case with the extension <code>.png .jpg .jpeg</code> and the file size limit to <code>50K </code></li>
        <li>After selecting the file and not starting to upload the file, click the <code> submit </code> button to verify that the data is legitimate, and then upload the file <code> OnSubmit </code> callback delegate, noting that the <b> Picture </b> property type is <code> IBrowserFile </code></li>
    </ul>
    <ValidateForm Model="Foo" OnValidSubmit="OnSubmit">
        <div class="row g-3">
            <div class="col-12">
                <BootstrapInput @bind-Value="@Foo.Name" />
            </div>
            <div class="col-12">
                <InputUpload @bind-Value="@Foo.Picture" />
            </div>
            <div class="col-12">
                <Button ButtonType="@ButtonType.Submit" Text="Submit"></Button>
            </div>
        </div>
    </ValidateForm>
</DemoBlock>

<DemoBlock Title="Click upload" Introduction="The <code> ButtonUpload </code> components, classic styles, user click button to pop up the file selection box." Name="ClickUpload">
    <div class="row g-3">
        <div class="col-12 col-sm-6">
            <p>Click on the <code> browse button </code> select file upload, in this case set <code>IsMultiple-true</code> multiple-selectable file can be uploaded</p>
            <ButtonUpload TValue="string" IsMultiple="true" ShowProgress="true" OnChange="@OnClickToUpload" OnDelete="@(fileName => Task.FromResult(true))"></ButtonUpload>
        </div>
    </div>
    <p class="mt-3">When you set up <code> IsSingle </code>, you can upload only one image or file</p>
    <ButtonUpload TValue="string" IsSingle="true" OnChange="@OnClickToUpload" OnDelete="@(fileName => Task.FromResult(true))" class="mt-3"></ButtonUpload>
</DemoBlock>

<DemoBlock Title="A list of files has been uploaded" Introduction="Use <code> DefaultFileList </code> to set up uploaded content" Name="UploadedFiles">
    <div class="row g-3">
        <div class="col-12 col-sm-6">
            <ButtonUpload TValue="string" OnDelete="@(fileName => Task.FromResult(true))" DefaultFileList="@DefaultFormatFileList"></ButtonUpload>
        </div>
    </div>
</DemoBlock>

<DemoBlock Title="Upload a folder" Introduction="Use <code> DefaultFileList </code> to set up uploaded content" Name="UploadFolder">
    <div class="row g-3">
        <div class="col-12 col-sm-6">
            <ButtonUpload TValue="string" IsDirectory="true" OnChange="@OnUploadFolder" OnDelete="@(fileName => Task.FromResult(true))"></ButtonUpload>
        </div>
    </div>
</DemoBlock>

<DemoBlock Title="User profile picture upload" Introduction="<code> AvatarUpload </code> component, using the <code>OnChange </code> to limit the format and size of images uploaded by users. In this example, only <code>jpg/png/bmp/jpeg/gif </code> five picture formats are allowed" Name="AvatarUpload">
    <div class="row g-3">
        <div class="col-12">
            <p>Card form avatar box</p>
            <AvatarUpload TValue="string" Accept="image/*" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
        </div>
        <div class="col-12">
            <p>Round avatar frame</p>
            <AvatarUpload TValue="string" Accept="image/*" ShowProgress="true" IsCircle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
        </div>
        <div class="col-12">
            <p>When you set up <code> IsSingle </code>, you can upload only one image or file</p>
            <AvatarUpload TValue="string" IsSingle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
            <p>RELATED: <a href='https://www.runoob.com/tags/att-input-accept.html' target='_blank'>[Accept]</a>  < a href='http://www.iana.org/assignments/media-types/media-types.xhtml' target='_blank' >[Media Types]</a></p>
            <p></p>
            <AvatarUpload TValue="string" Accept="image/gif, image/jpeg" IsSingle="true" OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))"></AvatarUpload>
        </div>
        <div class="col-12">
            <p>Set the preview address <code>PrevUrl </code> with the <code> DefaultFileList </code> property</p>
            <AvatarUpload TValue="string" Accept="image/gif, image/jpeg" IsSingle="true" DefaultFileList="@PreviewFileList"
                          OnChange="@OnAvatarUpload" OnDelete="@(fileName => Task.FromResult(true))" />
        </div>
        <div class="col-12">
            <p>Verify that an example of using a picture box is used in the form</p>
            <ValidateForm Model="Foo" OnValidSubmit="OnAvatarValidSubmit">
                <div class="row g-3">
                    <div class="col-12">
                        <BootstrapInput @bind-Value="@Foo.Name" />
                    </div>
                    <div class="col-12">
                        <AvatarUpload @bind-Value="@Foo.Picture" IsSingle="true" />
                    </div>
                    <div class="col-12">
                        <Button ButtonType="@ButtonType.Submit" Text="Submit"></Button>
                    </div>
                </div>
            </ValidateForm>
        </div>
    </div>
    <BlockLogger @ref="AvatarTrace" class="mt-3" />
</DemoBlock>

<DemoBlock Title="Preview the card style" Introduction="<code> CardUpload </code> components and rendered in card-style band preview mode" Name="PreCardStyle">
    <p>
        <div><b> SSR mode </b></div>
        <div><code> Server Side </code> mode, you can use the <code>IWebHostEnvironment </code> injection service to get to the <code> wwwroot </code> directory and save the file to the <code>images/uploader </code>, which does not require a direct call the controller secondary of <b>  MVC </b> <code> SaveToFile </code> method</div>

        <div><b> Wasm mode </b></div>
        <div>It wasn't available in <code> wasm </code> mode, <code> IWebHostEnvironment </code> needed to save files to the server side by calling <code> webapi </code> interface, and so on</div>
        <div>@((MarkupString)Localizer["PreCardStyleLink", SiteOptions.Value.VideoLibUrl].Value)</div>
        <div>In this example, server-side verification prompts the file for too much prompt when the file size exceeds <code>200MB </code></div>
    </p>
    <div class="row g-3">
        <div class="col-12">
            <p>In this example, the <code> ShowProgress=true </code> display upload progress bar</p>
            <CardUpload TValue="string" ShowProgress="true" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
        </div>
        <div class="col-12">
            <p>When you set up <code> IsSingle </code>, you can upload only one image or file</p>
            <CardUpload TValue="string" IsSingle="true" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
        </div>
    </div>
</DemoBlock>

<DemoBlock Title="The file icon" Introduction="Icons are displayed in different file formats" Name="FileIcon">
    <CardUpload TValue="string" DefaultFileList="@DefaultFormatFileList" OnChange="@OnCardUpload" OnDelete="@(fileName => Task.FromResult(true))"></CardUpload>
</DemoBlock>

<AttributeTable Items="@GetInputAttributes()" Title="InputUpload" />

<AttributeTable Items="@GetButtonAttributes()" Title="ButtonUpload/CardUpload" />

<AttributeTable Items="@GetAvatarAttributes()" Title="AvatarUpload" />
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/

using BootstrapBlazor.Components;
using BootstrapBlazor.Shared.Common;
using BootstrapBlazor.Shared.Components;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace BootstrapBlazor.Shared.Samples;

/// <summary>
/// 
/// </summary>
public sealed partial class Uploads : IDisposable
{
    private static readonly Random random = new();

    [Inject]
    [NotNull]
    private ToastService? ToastService { get; set; }

    [Inject]
    [NotNull]
    private IOptions<WebsiteOptions>? SiteOptions { get; set; }

    [Inject]
    [NotNull]
    private IStringLocalizer<Uploads>? Localizer { get; set; }

    private List<UploadFile> PreviewFileList { get; } = new(new[] { new UploadFile { PrevUrl = "_content/BootstrapBlazor.Shared/images/Argo.png" } });

    private BlockLogger? Trace { get; set; }

    private List<UploadFile> DefaultFormatFileList { get; } = new List<UploadFile>()
        {
            new UploadFile { FileName = "Test.xls" },
            new UploadFile { FileName = "Test.doc" },
            new UploadFile { FileName = "Test.ppt" },
            new UploadFile { FileName = "Test.mp3" },
            new UploadFile { FileName = "Test.mp4" },
            new UploadFile { FileName = "Test.pdf" },
            new UploadFile { FileName = "Test.cs" },
            new UploadFile { FileName = "Test.zip" },
            new UploadFile { FileName = "Test.txt" },
            new UploadFile { FileName = "Test.dat" }
        };

    private Task OnFileChange(UploadFile file)
    {
        // 未真正保存文件
        // file.SaveToFile()
        Trace?.Log($"{file.File!.Name} {Localizer["Success"]}");
        return Task.FromResult("");
    }

    private async Task OnClickToUpload(UploadFile file)
    {
        // 示例代码,模拟 80% 几率保存成功
        var error = random.Next(1, 100) > 80;
        if (error)
        {
            file.Code = 1;
            file.Error = Localizer["Error"];
        }
        else
        {
            await SaveToFile(file);
        }
    }

    private CancellationTokenSource? UploadFolderToken { get; set; }
    private async Task OnUploadFolder(UploadFile file)
    {
        // 上传文件夹时会多次回调此方法
        await SaveToFile(file);
    }

    private CancellationTokenSource? ReadAvatarToken { get; set; }
    private async Task OnAvatarUpload(UploadFile file)
    {
        // 示例代码,使用 base64 格式
        if (file != null && file.File != null)
        {
            var format = file.File.ContentType;
            if (CheckValidAvatarFormat(format))
            {
                ReadAvatarToken ??= new CancellationTokenSource();
                if (ReadAvatarToken.IsCancellationRequested)
                {
                    ReadAvatarToken.Dispose();
                    ReadAvatarToken = new CancellationTokenSource();
                }

                await file.RequestBase64ImageFileAsync(format, 640, 480, MaxFileLength, ReadAvatarToken.Token);
            }
            else
            {
                file.Code = 1;
                file.Error = Localizer["FormatError"];
            }

            if (file.Code != 0)
            {
                await ToastService.Error(Localizer["AvatarMsg"], $"{file.Error} {format}");
            }
        }
    }

    private CancellationTokenSource? ReadToken { get; set; }

    private static long MaxFileLength => 200 * 1024 * 1024;

    private async Task OnCardUpload(UploadFile file)
    {
        if (file != null && file.File != null)
        {
            // 服务器端验证当文件大于 2MB 时提示文件太大信息
            if (file.Size > MaxFileLength)
            {
                await ToastService.Information(Localizer["FileMsg"], Localizer["FileError"]);
                file.Code = 1;
                file.Error = Localizer["FileError"];
            }
            else
            {
                await SaveToFile(file);
            }
        }
    }

    private async Task<bool> SaveToFile(UploadFile file)
    {
        // Server Side 使用
        // Web Assembly 模式下必须使用 webapi 方式去保存文件到服务器或者数据库中
        // 生成写入文件名称
        var ret = false;
        if (!string.IsNullOrEmpty(SiteOptions.Value.WebRootPath))
        {
            var uploaderFolder = Path.Combine(SiteOptions.Value.WebRootPath, $"images{Path.DirectorySeparatorChar}uploader");
            file.FileName = $"{Path.GetFileNameWithoutExtension(file.OriginFileName)}-{DateTimeOffset.Now:yyyyMMddHHmmss}{Path.GetExtension(file.OriginFileName)}";
            var fileName = Path.Combine(uploaderFolder, file.FileName);

            ReadToken ??= new CancellationTokenSource();
            ret = await file.SaveToFile(fileName, MaxFileLength, ReadToken.Token);

            if (ret)
            {
                // 保存成功
                file.PrevUrl = $"images/uploader/{file.FileName}";
            }
            else
            {
                var errorMessage = $"{Localizer["SaveFileError"]} {file.OriginFileName}";
                file.Code = 1;
                file.Error = errorMessage;
                await ToastService.Error(Localizer["UploadFile"], errorMessage);
            }
        }
        else
        {
            file.Code = 1;
            file.Error = Localizer["WasmError"];
            await ToastService.Information(Localizer["SaveFile"], Localizer["SaveFileMsg"]);
        }
        return ret;
    }

    private static bool CheckValidAvatarFormat(string format)
    {
        return "jpg;png;bmp;gif;jpeg".Split(';').Any(f => format.Contains(f, StringComparison.OrdinalIgnoreCase));
    }

    private Task<bool> OnFileDelete(UploadFile item)
    {
        Trace?.Log($"{item.OriginFileName} {Localizer["RemoveMsg"]}");
        return Task.FromResult(true);
    }

    private Person Foo { get; set; } = new Person();

    private static Task OnSubmit(EditContext context)
    {
        // 示例代码请根据业务情况自行更改
        // var fileName = Foo.Picture?.Name;
        return Task.CompletedTask;
    }

    [NotNull]
    private BlockLogger? AvatarTrace { get; set; }

    private Task OnAvatarValidSubmit(EditContext context)
    {
        AvatarTrace.Log(Foo.Picture?.Name ?? "");
        return Task.CompletedTask;
    }

    private class Person
    {
        [Required]
        [StringLength(20, MinimumLength = 2)]
        public string Name { get; set; } = "Blazor";

        [Required]
        [FileValidation(Extensions = new string[] { ".png", ".jpg", ".jpeg" }, FileSize = 50 * 1024)]
        public IBrowserFile? Picture { get; set; }
    }

    /// <summary>
    /// 获得属性方法
    /// </summary>
    /// <returns></returns>
    private IEnumerable<AttributeItem> GetInputAttributes() => new AttributeItem[]
    {
            new AttributeItem() {
                Name = "ShowDeleteButton",
                Description = Localizer["ShowDeleteButton"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsDisabled",
                Description = Localizer["IsDisabled"],
                Type = "boolean",
                ValueList = "true / false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "PlaceHolder",
                Description = Localizer["PlaceHolder"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "Accept",
                Description = Localizer["Accept"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "BrowserButtonClass",
                Description = Localizer["BrowserButtonClass"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = "btn-primary"
            },
            new AttributeItem() {
                Name = "BrowserButtonIcon",
                Description = Localizer["BrowserButtonIcon"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = "fa fa-folder-open-o"
            },
            new AttributeItem() {
                Name = "BrowserButtonText",
                Description = Localizer["BrowserButtonText"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = ""
            },
            new AttributeItem() {
                Name = "DeleteButtonClass",
                Description = Localizer["DeleteButtonClass"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = "btn-danger"
            },
            new AttributeItem() {
                Name = "DeleteButtonIcon",
                Description = Localizer["DeleteButtonIcon"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = "fa fa-trash-o"
            },
            new AttributeItem() {
                Name = "DeleteButtonText",
                Description = Localizer["DeleteButtonText"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = Localizer["DeleteButtonTextDefaultValue"]
            },
            new AttributeItem() {
                Name = "OnDelete",
                Description = Localizer["OnDelete"],
                Type = "Func<string, Task<bool>>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnChange",
                Description = Localizer["OnChange"],
                Type = "Func<UploadFile, Task>",
                ValueList = " — ",
                DefaultValue = " — "
            },
    };

    private IEnumerable<AttributeItem> GetButtonAttributes() => new AttributeItem[]
    {
            new AttributeItem() {
                Name = "IsDirectory",
                Description = Localizer["IsDirectory"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsMultiple",
                Description = Localizer["IsMultiple"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsSingle",
                Description = Localizer["IsSingle"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "ShowProgress",
                Description = Localizer["ShowProgress"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "Accept",
                Description = Localizer["Accept"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "BrowserButtonClass",
                Description = Localizer["BrowserButtonClass"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = "btn-primary"
            },
            new AttributeItem() {
                Name = "BrowserButtonIcon",
                Description = Localizer["BrowserButtonIcon"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = "fa fa-folder-open-o"
            },
            new AttributeItem() {
                Name = "BrowserButtonText",
                Description = Localizer["BrowserButtonText"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = ""
            },
            new AttributeItem() {
                Name = "DefaultFileList",
                Description = Localizer["DefaultFileList"],
                Type = "List<UploadFile>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnGetFileFormat",
                Description = Localizer["OnGetFileFormat"],
                Type = "Func<string, string>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnDelete",
                Description = Localizer["OnDelete"],
                Type = "Func<string, Task<bool>>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnChange",
                Description = Localizer["OnChange"],
                Type = "Func<UploadFile, Task>",
                ValueList = " — ",
                DefaultValue = " - "
            }
    };

    private IEnumerable<AttributeItem> GetAvatarAttributes() => new AttributeItem[]
    {
            new AttributeItem() {
                Name = "Width",
                Description = Localizer["Width"],
                Type = "int",
                ValueList = " — ",
                DefaultValue = "0"
            },
            new AttributeItem() {
                Name = "Height",
                Description = Localizer["Height"],
                Type = "int",
                ValueList = "—",
                DefaultValue = "0"
            },
            new AttributeItem() {
                Name = "IsCircle",
                Description = Localizer["IsCircle"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "IsSingle",
                Description = Localizer["IsSingle"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "ShowProgress",
                Description = Localizer["ShowProgress"],
                Type = "bool",
                ValueList = "true|false",
                DefaultValue = "false"
            },
            new AttributeItem() {
                Name = "Accept",
                Description = Localizer["Accept"],
                Type = "string",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "DefaultFileList",
                Description = Localizer["DefaultFileList"],
                Type = "List<UploadFile>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnDelete",
                Description = Localizer["OnDelete"],
                Type = "Func<string, Task<bool>>",
                ValueList = " — ",
                DefaultValue = " — "
            },
            new AttributeItem() {
                Name = "OnChange",
                Description = Localizer["OnChange"],
                Type = "Func<UploadFile, Task>",
                ValueList = " — ",
                DefaultValue = " — "
            },
    };

    /// <summary>
    /// 
    /// </summary>
    public void Dispose()
    {
        UploadFolderToken?.Dispose();
        ReadAvatarToken?.Cancel();
        ReadToken?.Cancel();
        GC.SuppressFinalize(this);
    }
}

B 站相关视频链接

交流群

QQ Group:BootstrapAdmin & Blazor 795206915 675147445 Welcome to join the group discussion
Themes
Bootstrap
Motronic
Ant Design (完善中)
LayUI (完善中)
An error has occurred. This application may no longer respond until reloaded. Reload