skip to Main Content

When consuming a component in another razor component, there is usually a need to call async methods triggered by an EventCallback property, onClick for instance. Is there any particular way to call it to assure asynchronicity?

Let’s assume the following example:

@* CASE 1*@
<MyButton OnClick=DoSomething>Click Me</MyButton>

@* CASE 2*@
<MyButton OnClick=@(async () => await DoSomething())>Click Me</MyButton>

@code {
    private async Task DoSomething(){ ... }
}

After compiling, is there any distinction between using a delegate or calling the function directly? Should we prefer one or the other?

This question doesn’t derive from an error or code running improperly but only from the fact that I get no feedback from Visual Studio and probably for a good reason but I would like to know if I’m writing improper code either way.

Choice Remark: Hard to pick a single answer for all provide valid points to the discussion, further reading is encouraged. It seems the confusion stems from a misunderstanding of the delegate’s role in the call stack, in that way I think that Shaun’s answer shows the most succinct and explicit example of that.

3

Answers


  1. Lambda expressions are fully supported and in fact, in some cases they may be required. Consider you are doing a @foreach(item in items) to pass the item to the function you’d likely need to use a lambda expression:

    @(async () => await DoSomething(item))

    Here is Microsoft documentation supporting lambas:

    https://learn.microsoft.com/en-us/aspnet/core/blazor/components/event-handling?view=aspnetcore-7.0

    Login or Signup to reply.
  2. In this specific case, the first is better.

    This code block is wrapping a Task within a Task. It wasteful on resources: each Task is a state machine object.

    OnClick=@(async () => await DoSomething())>
    

    However, what actually happens depends on MyButton.

    Here’s a demo version. I’ve added two button event handlers, with comments on how each executes.

    <button @attributes=this.AdditionalAttributes @onclick=this.OnButtonClickAsync></button>
    
    @code {
        [Parameter] public EventCallback<MouseEventArgs> OnClick { get; set; }
        [Parameter] public RenderFragment? ChildContent { get; set; }
        [Parameter(CaptureUnmatchedValues = true)] public Dictionary<string, object>? AdditionalAttributes { get; set; }
    
        private async Task OnButtonClickAsync(MouseEventArgs e)
        {
            // you are awaiting the delegate in the parent component
            // if it yields you'll await it's completion
            await this.OnClick.InvokeAsync(e);
            // Code here will only execute after the parent delegate has completed, awaits and all
            // any exceptions will bubble up to here 
        }
    
        private void OnButtonClick(MouseEventArgs e)
        {
            // This is a Fire and Forget call
            this.OnClick.InvokeAsync(e);
            // code here will execute as soon as the parent delegate yields
            // exceptions won't bubble up to here
        }
    }
    

    More generally, it’s a matter of personal preference.

    I like clean markup, so I code like this.

    <button disabled="@buttonCss"></button>
    
    @code {
        private bool _isDisabled;
        private string buttonCss => _isDisabled ? "btn btn-danger" : "btn btn-success";
    }
    

    Others like succinct [inline] code.

    <button disabled="@(_isDisabled ? "btn btn-danger": "btn btn-success")"></button>
    
    @code {
        private bool _isDisabled;
    }
    
    Login or Signup to reply.
  3. The following snippet taken straight out of Microsoft docs:

    In the following example, UpdateHeading:

    • Is called asynchronously when the button is selected.
    • Waits two seconds before updating the heading.

    Pages/EventHandlerExample2.razor:

    @page "/event-handler-example-2"
    
    <h1>@currentHeading</h1>
    
    <p>
        <label>
            New title
            <input @bind="newHeading" />
        </label>
        <button @onclick="UpdateHeading">
            Update heading
        </button>
    </p>
    
    @code {
        private string currentHeading = "Initial heading";
        private string? newHeading;
    
        private async Task UpdateHeading()
        {
            await Task.Delay(2000);
    
            currentHeading = $"{newHeading}!!!";
        }
    }
    

    As you can see lambda is not used (@onclick="UpdateHeading") but still it is mentioned that UpdateHeading will be called asynchronously.

    Also when passing parameters, wrapping in async await is not required. The following will work correctly:

    @page "/event-handler-example-2"
    
    <h1>@currentHeading</h1>
    
    <p>
        <label>
            New title
            <input @bind="newHeading" />
        </label>
        <button @onclick="() => UpdateHeading(2000)">
            Update heading
        </button>
    </p>
    
    @code {
        private string currentHeading = "Initial heading";
        private string? newHeading;
    
        private async Task UpdateHeading(int delay)
        {
            await Task.Delay(delay);
    
            currentHeading = $"{newHeading}!!!";
        }
    }
    

    A lambda with async/await would be useful if instead of declaring UpdateHeading, you wanted to use anonymous method:

    @page "/event-handler-example-2"
    
    <h1>@currentHeading</h1>
    
    <p>
        <label>
            New title
            <input @bind="newHeading" />
        </label>
        <button @onclick="@(async () => { await Task.Delay(2000); currentHeading = $"{newHeading}!!!"; })">
            Update heading
        </button>
    </p>
    
    @code {
        private string currentHeading = "Initial heading";
        private string? newHeading;
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search