【问题标题】:Showing a modal dialog message and awaiting user answer from blazor code behind显示模式对话框消息并等待来自后面的 blazor 代码的用户回答
【发布时间】:2020-08-27 15:14:14
【问题描述】:

我关注this guide 为我的Blazor 应用程序显示模态对话框。我知道Blazored.Modal,这里没有用于学习目的。

这里的重点是,我想将其用于某种用户验证,并且仅在用户要求时才执行代码。我使用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 方法,显示模态对话框并等待对话框关闭以返回答案?

【问题讨论】:

    标签: modal-dialog user-input blazor


    【解决方案1】:

    所以我的 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($"&nbsp;&nbsp;&nbsp;{message}&hellip;");
                _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,这里就不贴了,每个人都不一样。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-11
      • 1970-01-01
      • 2020-10-03
      • 2014-06-19
      • 1970-01-01
      相关资源
      最近更新 更多