MudBlazor 之 MudForm + FluentValidation 紀錄
為工作紀錄。
參考文件
引言
用 MudForm 開發比較進階的UI操作介面。因為滿麻煩的所以留下紀錄未來參考。
開發環境
IDE: Visual Studio 2022
平台: .NET6
骨架: Blaozr Server App
CSS Framework: MudBlaozr v6.1.9
UI Validator: FluentValidation v11.5.1
紀錄:form data model / DTO - 主檔/明細檔
using FluentValidation;
public class YourFormData
{
/// 基本欄位
public PageSetting info { get; set; } = new();
/// 附件清單
public List<PageAttachFile> attachList { get; set; } = new();
/// 外部參考文件清單
public List<PageRefLink> referList { get; set; } = new();
}
// inner DTO class definition
public class PageSetting { ... }
public class PageAttachFile { ... }
public class PageRefLink { ... }
public class YourFormDataValidator : AbstractValidator<YourFormData>
{
public YourFormDataValidator()
{
// 這樣也行 - 檢查群組物件
//RuleFor(m => m.info.Title).NotEmpty();
//RuleFor(m => m.info.BannerUrl).NotEmpty();
//RuleFor(m => m.info.Content).NotEmpty();
// 檢查群組物件
RuleFor(m => m.info)
.SetValidator(new PageSettingValidator());
// 檢查明細清單
RuleForEach(m => m.attachList)
.SetValidator(new PageAttachFileValidator());
// 檢查明細清單
RuleForEach(m => m.referList)
.SetValidator(new PageRefLinkValidator());
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<YourFormData>.CreateWithOptions((YourFormData)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
public class PageSettingValidator : AbstractValidator<PageSetting>
{
public PageSettingValidator()
{
RuleFor(m => m.Title).NotEmpty();
RuleFor(m => m.BannerUrl).NotEmpty();
RuleFor(m => m.Content).NotEmpty();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<PageSetting>.CreateWithOptions((PageSetting)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
public class PageAttachFileValidator : AbstractValidator<PageAttachFile>
{
public PageAttachFileValidator()
{
RuleFor(m => m.FileDesc).NotEmpty();
When(c => String.IsNullOrWhiteSpace(c.Pdf) && String.IsNullOrWhiteSpace(c.Odt) && String.IsNullOrWhiteSpace(c.Doc), () =>
{
RuleFor(m => m.Pdf).Must(m => false).WithMessage("至少指定一份附件。");
RuleFor(m => m.Odt).Must(m => false).WithMessage("至少指定一份附件。");
RuleFor(m => m.Doc).Must(m => false).WithMessage("至少指定一份附件。");
});
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<PageAttachFile>.CreateWithOptions((PageAttachFile)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
public class PageRefLinkValidator : AbstractValidator<PageRefLink>
{
public PageRefLinkValidator()
{
RuleFor(m => m.RefDesc).NotEmpty();
RuleFor(m => m.RefUrl).NotEmpty();
}
public Func<object, string, Task<IEnumerable<string>>> ValidateValue => async (model, propertyName) =>
{
var result = await ValidateAsync(ValidationContext<PageRefLink>.CreateWithOptions((PageRefLink)model, x => x.IncludeProperties(propertyName)));
if (result.IsValid)
return Array.Empty<string>();
return result.Errors.Select(e => e.ErrorMessage);
};
}
紀錄: 前端 Blazor Component
@inject IDialogServiceEx dlgSvc
@inject YourFormBiz bizSvc
<CustomOverlay Visible=f_loading />
<MudContainer>
<MudForm @ref=refForm Model=@formData Validation=@(formDataValidator.ValidateValue)>
<MudField Label="文章識別碼">@formData.info.PageID</MudField>
@* 這樣也行 - 群組物件UI
<MudTextField Label="文章抬頭" @bind-Value=@formData.info.Title For="()=>formData.info.Title" />
<MudTextField Label="橫幅圖片" @bind-Value=@formData.info.BannerUrl For="()=>formData.info.BannerUrl" />
<MudTextField Label="文字段落" @bind-Value=@formData.info.Content For="()=>formData.info.Content" />
*@
@* 群組物件UI *@
<MudForm Model=@formInfo Validation=@(infoValidator.ValidateValue)>
<MudTextField Label="文章抬頭" @bind-Value=@formInfo.Title For="()=>formInfo.Title" />
<MudTextField Label="橫幅圖片" @bind-Value=@formInfo.BannerUrl For="()=>formInfo.BannerUrl" />
<MudTextField Label="文字段落" @bind-Value=@formInfo.Content For="()=>formInfo.Content" />
</MudForm>
<MudDivider Class="my-2" />
@* 明細清單UI *@
<MudTable Items=@formData.attachList Hover Dense=false Elevation="1" Context="attach">
<HeaderContent>
<MudTh>FileDesc</MudTh>
<MudTh>Pdf</MudTh>
<MudTh>Odt</MudTh>
<MudTh>Doc</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="FileDesc">
<MudForm Model=@attach Validation=@(attachValidator.ValidateValue)>
<MudTextField Label="FileDesc"
@bind-Value=@attach.FileDesc
For="() => attach.FileDesc" />
</MudForm>
</MudTd>
<MudTd DataLabel="Pdf">
<MudForm Model=@attach>
<MudTextField Label="Pdf"
@bind-Value=@attach.Pdf
Validation=@(attachValidator.ValidateValue)
For="(() => attach.Pdf)" />
</MudForm>
</MudTd>
<MudTd DataLabel="Odt">
<MudForm Model=@attach>
<MudTextField Label="Odt"
@bind-Value=@attach.Odt
Validation=@(attachValidator.ValidateValue)
For="(() => attach.Odt)" />
</MudForm>
</MudTd>
<MudTd DataLabel="Doc">
<MudForm Model=@attach>
<MudTextField Label="Doc"
@bind-Value=@attach.Doc
Validation=@(attachValidator.ValidateValue)
For="(() => attach.Doc)" />
</MudForm>
</MudTd>
</RowTemplate>
</MudTable>
<MudDivider Class="my-2" />
@* 另一個明細清單UI *@
<MudTable Items=@formData.referList Hover Dense=false Elevation="1" Context="refer">
<HeaderContent>
<MudTh>RefDesc</MudTh>
<MudTh>RefUrl</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="RefDesc">
<MudForm Model=@refer Validation=@(referValidator.ValidateValue)>
<MudTextField Label="RefDesc"
@bind-Value=@refer.RefDesc
For="() => refer.RefDesc" />
</MudForm>
</MudTd>
<MudTd DataLabel="RefUrl">
<MudForm Model=@refer Validation=@(referValidator.ValidateValue)>
<MudTextField Label="RefUrl"
@bind-Value=@refer.RefUrl
For="() => refer.RefUrl" />
</MudForm>
</MudTd>
</RowTemplate>
</MudTable>
</MudForm>
<MudStack Row Class="my-2">
<MudButton OnClick=HandleSubmit>送出</MudButton>
</MudStack>
</MudContainer>
@code {
[CascadingParameter] _YourFuncStateStore BizState { get; set; }
//## State
bool f_loading = false;
YoutFormData formData { get => BizState.formData; set => BizState.formData = value; }
PageSetting formInfo { get => BizState.formData.info; set => BizState.formData.info = value; }
MudForm? refForm;
YoutFormDataValidator formDataValidator = new();
PageSettingValidator infoValidator = new();
PageAttachFileValidator attachValidator = new();
PageRefLinkValidator referValidator = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
f_loading = true;
formData = await Task.Run(() => bizSvc.GetFormData(BizState.dataAim));
f_loading = false;
}
async Task HandleSubmit()
{
try
{
//# 基本檢查
await refForm.Validate();
if (!refForm.IsValid) return;
//# GO
f_loading = true;
...省略...
}
catch (Exception ex)
{
dlgSvc.ShowAlert(ex.Message, "更新失敗!");
}
finally
{
f_loading = false;
}
}
}
Last updated