1.新建工程
源码
新建工程b18ValidateForm,使用 nuget.org 进行 BootstrapBlazor 组件安装, Chart 库,字体. 将项目添加到解决方案中
dotnet new blazorserver -o b18ValidateForm
dotnet add b06chart package BootstrapBlazor
dotnet add b06chart package BootstrapBlazor.FontAwesome
dotnet sln add b18ValidateForm/b18ValidateForm.csproj
2.样式表和Javascript 引用
增加主题样式表到 Pages/_Layout.cshtml
文件中
删除 <link rel="stylesheet" href="css/bootstrap/bootstrap.min.css" />
并在下面添加三行
<link href="_content/BootstrapBlazor.FontAwesome/css/font-awesome.min.css" rel="stylesheet" />
<link href="_content/BootstrapBlazor/css/bootstrap.blazor.bundle.min.css" rel="stylesheet" />
添加 Javascript 引用到 Pages/_Layout.cshtml
文件中
在 <script src="_framework/blazor.server.js"></script>
之前添加
<script src="_content/BootstrapBlazor/js/bootstrap.blazor.bundle.min.js" asp-append-version="true"></script>
3.添加增加命名空间引用到 _Imports.razor
文件中
@using BootstrapBlazor.Components
4.增加 BootstrapBlazorRoot 组件到 App.razor 文件中
<BootstrapBlazorRoot>
<Router AppAssembly="@typeof(App).Assembly">
...
</Router>
</BootstrapBlazorRoot>
5.添加BootstrapBlazor服务到 Program.cs
文件中
在 builder.Services.AddSingleton<WeatherForecastService>();
后加入
builder.Services.AddBootstrapBlazor();
6.添加EditorForm测试代码
Index.razor
<EditorForm Model="@Model">
<FieldItems>
<EditorItem @bind-Field="@context.Name" />
<EditorItem @bind-Field="@context.Education" />
<EditorItem @bind-Field="@context.Complete" />
</FieldItems>
<Buttons>
<Button Icon="fa-solid fa-floppy-disk" Text="提交" />
</Buttons>
</EditorForm>
Index.razor.cs
using BootstrapBlazor.Components;
using Microsoft.Extensions.Localization;
using System.Collections.Concurrent;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
namespace b18ValidateForm.Pages;
public sealed partial class Index
{
[NotNull]
private Foo? Model { get; set; }
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
{
Model = new Foo()
{
Name = "",
Count = 1,
Address = "TestAddress",
DateTime = new DateTime(1997, 12, 05),
Education = EnumEducation.Middle
};
}
}
Demo示例数据
/// <summary>
/// Demo示例数据
/// Demo sample data
/// </summary>
public class Foo
{
// 列头信息支持 Display DisplayName 两种标签
/// <summary>
///
/// </summary>
[Display(Name = "主键")]
[AutoGenerateColumn(Ignore = true)]
public int Id { get; set; }
/// <summary>
///
/// </summary>
[Required(ErrorMessage = "{0}不能为空")]
[AutoGenerateColumn(Order = 10, Filterable = true, Searchable = true)]
[Display(Name = "姓名")]
public string? Name { get; set; }
/// <summary>
///
/// </summary>
[AutoGenerateColumn(Order = 1, FormatString = "yyyy-MM-dd", Width = 180)]
[Display(Name = "日期")]
public DateTime? DateTime { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = "地址")]
[Required(ErrorMessage = "{0}不能为空")]
[AutoGenerateColumn(Order = 20, Filterable = true, Searchable = true)]
public string? Address { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = "数量")]
[Required]
[AutoGenerateColumn(Order = 40, Sortable = true)]
public int Count { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = "是/否")]
[AutoGenerateColumn(Order = 50)]
public bool Complete { get; set; }
/// <summary>
///
/// </summary>
[Required(ErrorMessage = "请选择学历")]
[Display(Name = "学历")]
[AutoGenerateColumn(Order = 60)]
public EnumEducation? Education { get; set; }
/// <summary>
///
/// </summary>
[Required(ErrorMessage = "请选择一种{0}")]
[Display(Name = "爱好")]
[AutoGenerateColumn(Order = 70, Editable = false)]
public IEnumerable<string> Hobby { get; set; } = new List<string>();
#region Static methods
/// <summary>
///
/// </summary>
protected static readonly Random Random = new();
/// <summary>
/// 生成Foo类,随机数据
/// Generate Foo class, random data
/// </summary>
/// <param name="localizer"></param>
/// <returns></returns>
public static Foo Generate(IStringLocalizer<Foo> localizer) => new()
{
Id = 1,
Name = localizer["Foo.Name", "1000"],
DateTime = System.DateTime.Now,
Address = localizer["Foo.Address", $"{Random.Next(1000, 2000)}"],
Count = Random.Next(1, 100),
Complete = Random.Next(1, 100) > 50,
Education = Random.Next(1, 100) > 50 ? EnumEducation.Primary : EnumEducation.Middle
};
/// <summary>
/// 生成 Foo 类,随机数据
/// Generate Foo class, random data
/// </summary>
/// <returns>返回一个Foo类的List,Return a List of Foo class</returns>
public static List<Foo> GenerateFoo(int count = 80) => Enumerable.Range(1, count).Select(i => new Foo()
{
Id = i,
Name = "Foo.Name"+ $"{i:d4}",
DateTime = System.DateTime.Now.AddDays(i - 1),
Address = "Foo.Address"+$"{Random.Next(1000, 2000)}",
Count = Random.Next(1, 100),
Complete = Random.Next(1, 100) > 50,
Education = Random.Next(1, 100) > 50 ? EnumEducation.Primary : EnumEducation.Middle
}).ToList();
/// <summary>
/// 通过 Count 获得颜色
/// </summary>
/// <param name="count"></param>
/// <returns></returns>
public static Color GetProgressColor(int count) => count switch
{
>= 0 and < 10 => Color.Secondary,
>= 10 and < 20 => Color.Danger,
>= 20 and < 40 => Color.Warning,
>= 40 and < 50 => Color.Info,
>= 50 and < 70 => Color.Primary,
_ => Color.Success
};
/// <summary>
/// 通过 Id 获取 Title
/// </summary>
/// <returns></returns>
private static string GetTitle() => Random.Next(1, 80) switch
{
>= 1 and < 10 => "Clerk",
>= 10 and < 50 => "Engineer",
>= 50 and < 60 => "Manager",
>= 60 and < 70 => "Chief",
_ => "General Manager"
};
/// <summary>
///
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public static string GetTitle(int id) => Cache.GetOrAdd(id, key => GetTitle());
/// <summary>
///
/// </summary>
/// <returns></returns>
public static Func<IEnumerable<Foo>, string, SortOrder, IEnumerable<Foo>> GetNameSortFunc() => Utility.GetSortFunc<Foo>();
private static ConcurrentDictionary<int, string> Cache { get; } = new();
#endregion
}
/// <summary>
///
/// </summary>
public enum EnumEducation
{
/// <summary>
///
/// </summary>
[Display(Name = "小学")]
Primary,
/// <summary>
///
/// </summary>
[Display(Name = "中学")]
Middle
}
7.运行
8.新需求: 在点击[是/否]的时候,动态控制姓名和地址栏只读
尝试把代码改为
<EditorForm Model="@Model">
<FieldItems>
@*<InputDIY Context="@context" />*@
@if (context.Complete)
{
<EditorItem @bind-Field="@context.Name" Readonly="@context.Complete" />
<EditorItem @bind-Field="@context.Education" Readonly="@context.Complete" />
}
<EditorItem @bind-Field="@context.DateTime" Readonly="true" />
<EditorItem @bind-Field="@context.Complete" />
</FieldItems>
<Buttons>
<Button Icon="fa-solid fa-floppy-disk" Text="提交" />
</Buttons>
</EditorForm>
运行之, 并没有达到预期. 无论怎么改变[是/否]检查框,姓名和地址栏都是可写的,因为渲染机制问题,所以要改一下思路.
正确方法是把逻辑包成一个组件,组件里面就可以局部刷新达到预期效果
9.新建组件 InputDIY.razor
<Row>
<BootstrapInput @bind-Value="@Context!.Name" IsDisabled="@Context!.Complete" ShowLabel="true" />
<BootstrapInput @bind-Value="@Context!.Education" IsDisabled="@Context!.Complete" ShowLabel="true" />
</Row>
<br />
@code{
[Parameter] public Foo? Context { get; set; }
}
Index.razor代码改为
<EditorForm Model="@Model">
<FieldItems>
<InputDIY Context="@context" />
<EditorItem @bind-Field="@context.Name" Editable="@context.Complete" />
<EditorItem @bind-Field="@context.Education" Editable="@context.Complete" />
<EditorItem @bind-Field="@context.DateTime" Readonly="true" />
<EditorItem @bind-Field="@context.Complete" />
</FieldItems>
<Buttons>
<Button Icon="fa-solid fa-floppy-disk" Text="提交" />
</Buttons>
</EditorForm>
注:如果不加入以下写法,会照成InputDIY里面渲染一次Name列,FieldItems又渲染一次Name列.这是MS的内部机制一个小坑,暂时没有办法避开.
<EditorItem @bind-Field="@context.Name" Editable="@context.Complete" />
10.运行效果
11.数据验证 ValidateForm
<ValidateForm Model="@Model">
<EditorForm TModel="Foo">
<FieldItems>
<InputDIY Context="@context" />
<EditorItem @bind-Field="@context.Name" Editable="@context.Complete" />
<EditorItem @bind-Field="@context.Education" Editable="@context.Complete" />
<EditorItem @bind-Field="@context.DateTime" Readonly="true" />
<EditorItem @bind-Field="@context.Complete" />
</FieldItems>
<Buttons>
<Button ButtonType="ButtonType.Submit" Icon="fa-solid fa-floppy-disk" Text='提交' />
</Buttons>
</EditorForm>
</ValidateForm>