---
title: "An awaitable Blazor Modal Dialog"
description: "I already showcased a simple modal dialog in a [previous post](https://steven-giesel.com/blogPost/5fc5b957-d62e-40e6-b0c4-f2a0df5c8aa1/modal-dialog-component-with-bootstrap-in-blazor). However, the dialog was not awaitable. In this post, I'll show you how to create an awaitable modal dialog.\n"
updated: 2024-11-11
tags: ["C#", ".NET", "Blazor"]
canonical: "https://steven-giesel.com/blogPost/79ab37d4-5fdf-4c36-8b99-ac17bbe1fc01/an-awaitable-blazor-modal-dialog"
---

# An awaitable Blazor Modal Dialog

I already showcased a simple modal dialog in a [previous post](https://steven-giesel.com/blogPost/5fc5b957-d62e-40e6-b0c4-f2a0df5c8aa1/modal-dialog-component-with-bootstrap-in-blazor). However, the dialog was not awaitable. In this post, I'll show you how to create an awaitable modal dialog.

## An awaitable modal dialog
While callbacks are very nice, sometimes it is just way more convenient to await a dialog. Something likes this:

```csharp
var result = await MyDialog.ShowAsync();
if (result is null)
{
    // The user pressed "Cancel"
}
else
{
    // The user pressed "Ok"
}
```

To make this work, we can utilize the power of `TaskCompletionSource<T>`. This class allows us to create a `Task<T>` that we can complete whenever we want. This is perfect for our use case. I also have a blog post about this: ["*Wrap Event based functions into awaitable Tasks - Meet TaskCompletionSource*"](https://steven-giesel.com/blogPost/9ad214ff-1f32-4ed0-a5ac-1b996e1e9a68/wrap-event-based-functions-into-awaitable-tasks-meet-taskcompletionsource).

## The Dialog

I used this recently in the following component, that asks the user for a filename and an optional flag:
```html
<div class="modal @modalClass" tabindex="-1" role="dialog" style="display:@modalDisplay; overflow-y: auto;">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Configuration</h5>
                <button type="button" class="btn-close" @onclick="OnAbort"></button>
            </div>
            <div class="modal-body">
                <EditForm Model="@model" OnValidSubmit="OnOk">
                    <DataAnnotationsValidator />
                    <div class="form-floating mb-3">
                        <InputText type="text" class="form-control" id="name" placeholder="Filename"
                            @bind-Value="model.Name" />
                        <label for="name">Filename</label>
                        <ValidationMessage For="() => model.Name"></ValidationMessage>
                    </div>
                    <div class="form-check form-switch mb-3">
                        <InputCheckbox class="form-check-input" id="cache" @bind-Value="model.CacheMedia" />
                        <label class="form-check-label" for="cache">Enable Media Caching</label><br />
                        <small class="form-text text-body-secondary">If enabled, the browser will cache the media file</small>
                    </div>
                </EditForm>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" @onclick="OnAbort">Abort</button>
                <button type="button" class="btn btn-primary" @onclick="OnOk">OK</button>
            </div>
        </div>
    </div>
</div>

@if (showBackdrop)
{
    <div class="modal-backdrop fade show"></div>
}

@code {
    private TaskCompletionSource<UploadFileModalDialogObject?> result = null!;
    private string modalDisplay = "none;";
    private string modalClass = "";
    private bool showBackdrop;
    private readonly UploadFileModalDialogObject model = new();

    public async Task<UploadFileModalDialogObject?> ShowAsync(string fileName)
    {
        modalDisplay = "block;";
        modalClass = "show";
        showBackdrop = true;
        model.Name = fileName;
        result = new TaskCompletionSource<UploadFileModalDialogObject?>();
        StateHasChanged();
        return await result.Task;
    }

    private void OnAbort()
    {
        modalDisplay = "none";
        modalClass = "";
        showBackdrop = false;
        result.SetResult(null);
        StateHasChanged();
    }

    private void OnOk()
    {
        modalDisplay = "none";
        modalClass = "";
        showBackdrop = false;
        result.SetResult(model);
        StateHasChanged();
    }
}
```

It looks like this:

![dialog](https://linkdotnetblog.azureedge.net/blog/20241106_AwaitableDialog/dialog.webp)

The interesting bid is the `ShowAsync` method. It sets up the dialog and returns the `Task` that we can await. The `OnAbort` and `OnOk` methods are called when the user presses the corresponding button. They set the result and close the dialog (with the given result).

As discussed in [*"Wrap Event based functions into awaitable Tasks - Meet TaskCompletionSource"*](https://steven-giesel.com/blogPost/9ad214ff-1f32-4ed0-a5ac-1b996e1e9a68/wrap-event-based-functions-into-awaitable-tasks-meet-taskcompletionsource), we can make event driven (aka the button clicks) functions awaitable by using `TaskCompletionSource<T>`. This is exactly what we did here.

## Usage

I have a component where I do open this dialog when the user drag 'n drops a file onto the page:
```html
<UploadFileModalDialog @ref="UploadDialog"></UploadFileModalDialog>

@code {
    private UploadFileModalDialog UploadDialog { get; set; } = default!;

    private async Task OnDrop(InputFileChangeEventArgs e)
    {
        var file = GetFile(e);

        var result = await UploadDialog.ShowAsync(file.Name);
        if (result is null)
        {
            return;
        }

        // Upload file
        ...
    }
}
```

## Conclusion
Creating an awaitable dialog is quite simple. We just need to use a `TaskCompletionSource<T>` to create a `Task<T>` that we can complete whenever we want. This allows us to await the dialog and get the result when the user closes the dialog.