我的Blazor
应用程序按照本指南显示模式对话框。我知道Blazored。此处未用于学习目的的模态。
这里的重点是,我想将其用于某种用户验证,并仅在用户要求时执行代码。我使用ModalService
在另一个MyBackgroundService
中显示提示,在某个时刻做一些需要用户选择的事情。
这是主页的代码:
@page "/"
@inject ModalService _modalService
@inject MyBackgroundService _myService
<div>
<button @onclick="onShowClick" class="btn btn-primary">Show</button>
<button @onclick="onRunClick" class="btn btn-primary">Run</button>
</div>
@code {
protected async Task onShowClick() {
// shows MyControl in a modal form => working just great!
_modalService.Show(typeof(MyControl));
}
protected async Task onRunClick() {
await _myService.Run();
}
}
这里是ModalService
类的代码:
public class ModalService {
public event Action<Type> OnShow;
public event Action OnClose;
public void Show(Type contentType) {
if (contentType.BaseType != typeof(ComponentBase)) {
throw new ArgumentException($"{contentType.FullName} must be a Blazor Component");
}
OnShow?.Invoke(contentType);
}
public void Close() {
OnClose?.Invoke();
}
}
这里是MyBackgroundService
类的示例代码:
public class MyBackgroundService {
private readonly ModalService _modalService;
public CalSyncerService(ModalService modalService) {
_modalService = modalService;
}
public async Task Run() {
var processResult1 = await firstLongProcess();
string userAnswer = "Ok";
if (processResult1 != true) {
// something went wrong so far => it might be risky to continue => ask user if Ok...
// of course, we're not awaiting the user answer here, this is what I need to correct!!
_modalService.Show(typeof(MyControl));
}
if (userAnswer == "Ok") {
await secondProcess();
}
}
}
等待用户回答的干净方式是什么?使用一个操作,或者更好的async
方法,显示模式对话框并等待对话框关闭以返回答案?
所以我的CLEAN方法涉及到一个带有布局引擎的组件,它还做其他事情,比如显示一个"工作";modal,包装在using中,所以它不会被遗留下来。
首次推出Razor组件:
@inherits LayoutEngineBase
@if (Spinning)
{
<div id="workingModal">
<h1>@Message</h1>
</div>
}
@if (NeedsConfirmation)
{
<div id="confirmModal">
<div class="card">
<div class="card-body">
<h3>@ConfirmationMessage</h3>
</div>
<div class="card-footer">
<button class="btn btn-success" @onclick="ConfirmYes">Ok</button>
<button class="btn btn-danger" @onclick="ConfirmNo">Cancel</button>
</div>
</div>
</div>
}
@ChildContent
然后它的基础:
using Microsoft.AspNetCore.Components;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Web.Shared
{
public class LayoutEngineBase : ComponentBase
{
/// <summary>
/// This is used for the contained Razor markup.
/// </summary>
[Parameter]
public RenderFragment ChildContent { get; set; }
/// <summary>
/// This fires when something has changed, useful with Cascaded Pages
/// </summary>
[Parameter]
public EventCallback OnStateChanged { get; set; }
/// <summary>
/// Bind this to whatever needs to change when working is in progress
/// </summary>
public bool Spinning { get; internal set; } = false;
/// <summary>
/// Bind this to whatever needs to change when a confirmation is required
/// </summary>
public bool NeedsConfirmation { get; internal set; } = false;
/// <summary>
/// MarkupString will render raw HTML to Razor.
/// </summary>
public MarkupString Message { get; internal set; }
protected MarkupString ConfirmationMessage { get; private set; }
/// <summary>
/// Call this internally to for a re-render StateHasChanged()
/// </summary>
internal async virtual void RaiseChange()
{
await OnStateChanged.InvokeAsync();
}
private CancellationTokenSource FinishConfirm;
private bool DialogResponse;
private string _stationName;
public string StationName
{
get => _stationName;
set
{
_stationName = value;
RaiseChange();
}
}
/// <summary>
/// Wrap this in a using to switch spinning state on and off at bracket boundaries
/// </summary>
/// <param name="message">Used to set the display message during working.</param>
/// <returns>New IDisposable Worker</returns>
public Worker Working(string message = null) => new Worker(this, message ?? "Working");
/// <summary>
/// Await this to wait for a user response.
/// </summary>
/// <param name="message"></param>
/// <returns>True for Ok, False for Cancel</returns>
public async Task<bool> ShowConfirmAsync(string message)
{
ConfirmationMessage = new(message);
NeedsConfirmation = true;
try
{
using (FinishConfirm = new())
{
await Task.Delay(-1, FinishConfirm.Token);
}
}
catch (TaskCanceledException) { } // we want to cancel it.
return DialogResponse;
}
protected void ConfirmYes()
{
ConfirmDialog(true);
}
protected void ConfirmNo()
{
ConfirmDialog(false);
}
private void ConfirmDialog(bool confirmation)
{
DialogResponse = confirmation;
if (FinishConfirm.Token.CanBeCanceled)
{
FinishConfirm.Cancel();
}
NeedsConfirmation = false;
RaiseChange();
}
}
/// <summary>
/// When created this will start the spinning, when disposed it will stop it. Not really needed by external code, but need to be public for accessibility level.
/// </summary>
public sealed class Worker : IDisposable
{
private LayoutEngineBase _parent;
public Worker(LayoutEngineBase parent, string message)
{
_parent = parent;
_parent.Message = new MarkupString($" {message}…");
_parent.Spinning = true;
_parent.RaiseChange();
}
public void Dispose()
{
_parent.Spinning = false;
_parent.RaiseChange();
}
}
}
然后,您可以用它包装MainLayout.razor:
<LayoutEngine @ref="layoutEngine" OnStateChanged="LayoutEngineStateChanged">
<div class="main grid">
<div class="top-row px-4">
...Navigation...
</div>
<div class="content p-4">
<CascadingValue Value=layoutEngine>
@Body
</CascadingValue>
</div>
</div>
</LayoutEngine>
注意,我已经@ref布局引擎,并级联它。
MainLayout:的代码
@code
{
protected LayoutEngineBase layoutEngine = new();
protected void LayoutEngineStateChanged()
{
StateHasChanged();
}
protected async void LogOut()
{
var confirm = await layoutEngine.ShowConfirmAsync("Are you sure?");
if (confirm)
{
await AuthenticationStateProvider.LogOutUserAsync();
StateHasChanged();
NavigationManager.NavigateTo("/");
}
}
}
正如您所看到的,LayoutEngine组件有一个OnStateChanged,然后您可以使用它来告诉MainLayout StateHasChanged((;
这里应该让你感兴趣的是LogOut函数,这里的var confirm-await ShowConfirm,它只在有人单击Ok或Cancel时结束任务。
此外;工作;选项可以与以下代码一起使用,只要您接受级联参数layoutEngine:
using (layoutEngine.Working("Doing Stuff"))
{
Do Stuff...
}
这示出了具有";做事;写在上面直到使用结束。
这显然需要一些CSS,我不会在这里发布,这对每个人来说都不一样。