所以我的 CLEAN 方式涉及到一个带有布局引擎的组件,它也做其他事情,比如显示一个“工作”模式,用 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 时结束任务。
此外,“Working”选项可以与以下代码一起使用,只要您接受级联参数 layoutEngine:
using (layoutEngine.Working("Doing Stuff"))
{
Do Stuff...
}
这显示了一个模式,上面写着“Doing Stuff”,直到使用结束。
这显然需要一点 CSS,这里就不贴了,每个人都不一样。