Introduction

Blazor modal component is a UI that can be used to display content to users in a separate layer above the main layer, which shows the main content without requiring navigation from the current page . It can be used for forms , confirmations or some content that needs a user interaction .


In a previous post, we created a Blazor confirm dialog component that can be used in your projects. In this post, we will see how to create a Blazor Modal Component with pure C#, Html and CSS.

Take a look at the finished Blazor Modal Component implemented in a Blazor Web App.

https://dotnetcoder.com/wp-content/uploads/2024/06/Creating-a-Reusable-Blazor-Modal-Component.mp4

Creating a Modal Solution and adding a Razor class library project

Open Visual Studio and create a blank solution named Modal . Add a new Razor Class library named Dnc.Common.Razor to the solution. Select .NET 8 (Long Term Support) as the target Framework.

Creating the DncModal component 

First, create a new folder in the Dnc.Common.Razor project named Modal. Then, create a new class file derived from the ComponentBase class called DncModalComponent, and add the following code.

namespace Dnc.Common.Razor.Modal
{
    public class DncModalComponent<TItem> : ComponentBase
    {
        [Parameter] public RenderFragment<TItem> HeaderTemplate { get; set; }
        [Parameter] public RenderFragment<TItem> BodyTemplate { get; set; }
        [Parameter] public RenderFragment<TItem> FooterTemplate { get; set; }

        [Parameter] public EventCallback<TItem> OnSubmit { get; set; }
        [Parameter] public EventCallback<TItem> OnShow { get; set; }
        [Parameter] public string Size { get; set; }

        public EditContext EditContext { get; protected set; }
        protected TItem Item { get; set; }

        protected bool IsVisible { get; set; }
        protected string ModalSize { get; set; } = string.Empty;

        public void SetEditContext(EditContext editContext)
        {
            EditContext = editContext;
        }

        public async Task Show(TItem item = default)
        {
            ModalSize = Size switch
            {
                "Small" => "modal-sm",
                "Medium" => string.Empty,
                "Larg" => "modal-lg",
                "ExtraLarg" => "modal-xl",
                _ => string.Empty,
            };

            IsVisible = true;
            Item = item;

            var task = OnShow.InvokeAsync(Item);
            if (task != null && !task.IsCompleted)
            {
                StateHasChanged();
                await task;
            }

            EditContext ??= new EditContext(new { });

            StateHasChanged();
        }

        public async Task HandleSubmit()
        {
            await OnSubmit.InvokeAsync(Item);
        }

        public void Close()
        {
            IsVisible = false;
            Item = default;
            EditContext = null;

            StateHasChanged();
        }
    }
}

We will go over the code and provide some explanation.

[Parameter] public RenderFragment<TItem> HeaderTemplate { get; set; }
[Parameter] public RenderFragment<TItem> BodyTemplate { get; set; }
[Parameter] public RenderFragment<TItem> FooterTemplate { get; set; }

We have defined three RenderFragment<TItem> parameter properties in the DncModalComponent  that allow us to inject the content of Blazor Modal Header, Body and Footer into the Blazor Modal component by specifying a template in the consuming Razor files for rendering each TItem.

[Parameter] public EventCallback<TItem> OnSubmit { get; set; }
[Parameter] public EventCallback<TItem> OnShow { get; set; }

Then we declared two EventCallback<TItem> parameters, so that the Blazor Modal component notifies consumers when the form is submitted or displayed .
The consuming component specifies which method to call when these events are triggered.

public async Task Show(TItem item = default)
{
     ModalSize = Size switch
     {
         "Small" => "modal-sm",
          "Medium" => string.Empty,
          "Larg" => "modal-lg",
          "ExtraLarg" => "modal-xl",
           _ => string.Empty,
      };

      IsVisible = true;
      Item = item;

      var task = OnShow.InvokeAsync(Item);
      if (task != null && !task.IsCompleted)
      {
          StateHasChanged();
          await task;
       }

       EditContext ??= new EditContext(new { });

       StateHasChanged();
}

public async Task HandleSubmit()
{
    await OnSubmit.InvokeAsync(Item);
 }

The Show method sets the size of the Modal dialog depending on the Size parameter, displays the Modal, and emits the OnShow event when the Blazor Modal is displayed, and notifies the component to be re-rendered by calling the StateHasChanged method, while the HandleSubmit method emits the OnSubmit event.

public void SetEditContext(EditContext editContext)
{
    EditContext = editContext;
}

SetEditContext is used by the consuming file to set the EditContext property, which keeps track of the current state of the form and any validation errors raised.

The markup of the DncModal component contains some HTML and Bootstrap CSS styles and it is self-explanatory. 
In the Modal folder, create a new file called DncModal.razor, which inherits from the DncModalComponent class, and enter the following markup.

@inherits DncModalComponent<TItem>
@typeparam TItem

@if(IsVisible){
    @if(EditContext != null){
        <EditForm EditContext="@EditContext" OnSubmit="@HandleSubmit">
            <div class="modal fade show dnc-modal-background" id="DncModal" style="display: block;" aria-modal="true" role="dialog">
                <div class="modal-dialog @ModalSize">
                    <div class="modal-content">
                        <div class="modal-header">
                            @if (HeaderTemplate != null)
                            {
                                @HeaderTemplate(Item)
                            }
                            else
                            {
                                <h5 class="modal-title">Header</h5>
                            }

                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="Close"></button>
                        </div>
                        <div class="modal-body">
                            @BodyTemplate(Item)
                        </div>
                        <div class="modal-footer">
                            @FooterTemplate(Item)
                        </div>
                    </div>
                </div>
            </div>
        </EditForm>
    }
}

Create a new stylesheet file in the Modal folder calledDncModal.razor.css and add the following styles.

.modal-dialog {
    margin-top: 7rem;
}

.dnc-modal-background {
    background-color: rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(15px);
}
Blazor Modal Class lib

Also read https://dotnetcoder.com/azure-key-vault-configuration-in-asp-net-core/

Using the Blazor Modal component in a Blazor Web App

In this section, we will learn how to use our custom Blazor Modal component in the Blazor application.

First, add a new Blazor Web App project template called Dnc.Modal.WebApp to the solution. I have selected .NET 8 (Long Term Support) as the target Framework.

Second reference the Dnc.Common.Razor project to the  Dnc.Modal.WebAppproject, and update the _Imports.razor as follows.

// Removed code for brevity
@using Dnc.Common.Razor.Modal

Then  include the bootstrap link in the App.razor file, as shown below.

<head>
    // Removed code for brevity
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
    <HeadOutlet @rendermode="InteractiveServer" />
</head>

Finally, add the following code to the HomeComponet.cs file, I have separated the markup from the code in the Home component for clarity.

namespace Dnc.Modal.WebApp.Components.Pages
{
    public class HomeComponent: ComponentBase
    {
        protected DncModal<Account> EditModal { get; set; }

        protected DncModal<Account> AddModal { get; set; }

        // Edit Modal Methods
        protected void EditModalSubmitted(Account account)
        {
            var exist = Accounts.FirstOrDefault(v => v.Id == account.Id);

            if (exist != null && EditModal.EditContext.Validate())
            {
                // Update the account in real projects
                EditModal.Close();
            }

        }

        protected void EditModalDisplayed(Account account)
        {
            EditModal.SetEditContext(new EditContext(account));
        }

        // Add Account Modal Methods
        protected Account AccountModel { get; set; } = new Account()
        {
            Id = Guid.NewGuid().ToString(),
            ExpireDate = DateTime.Now.AddYears(1)
        };

        protected void AddModalSubmitted(Account account)
        {
            var exist = Accounts.FirstOrDefault(v => v.Id == account.Id);

            if (exist == null && AddModal.EditContext.Validate())
            {
                Accounts.Add(account);
                AddModal.Close();
            };
        }

        protected void AddModalDisplayed(Account account)
        {
            AddModal.SetEditContext(new EditContext(account));
        }

        protected List<Account> Accounts = new List<Account>
    {
        new Account {Id = Guid.NewGuid().ToString() , Name = "John Smith", Email= "john.smith@coder.com",  Age = 20, ExpireDate = DateTime.Now.AddYears(1)},
        new Account {Id = Guid.NewGuid().ToString() , Name = "Sarah Johnson", Email= "Sarah.Johnson@coder.com", Age = 30, ExpireDate = DateTime.Now.AddYears(1)},
        new Account {Id = Guid.NewGuid().ToString() , Name = "Michael Brown", Email= "Michael.Brown@coder.com", Age = 40, ExpireDate = DateTime.Now.AddYears(1)},
        new Account {Id = Guid.NewGuid().ToString() , Name = "Emily Davis", Email= "Emily.Davis@coder.com", Age = 50, ExpireDate = DateTime.Now.AddYears(1)}
    };
    }

    public class Account
    {
        public string Id { get; set; }
        [Required]
        public string Name { get; set; }
        [Required]
        [EmailAddress(ErrorMessage = "Insert valid email address")]
        public string Email { get; set; }
        [Range(18, 80, ErrorMessage = "The Age should be between 18 and 65 years")]
        public int Age { get; set; }
        public DateTime ExpireDate { get; set; }
    }
}

I have defined two properties AddModal and EditModal because we need to use the Blazor Modal component as a variable, so we need a reference to it.

I will go through the code for creating a new account just for the sake for brevity.

protected void AddModalDisplayed(Account account)
{
    AddModal.SetEditContext(new EditContext(account));
}

AddModalDisplayed is responsible for setting the EditContext in the Blazor Modal component with the SetEditContext method in the DncModal , as you can see in the code snippet above.

protected void AddModalSubmitted(Account account)
{
    var exist = Accounts.FirstOrDefault(v => v.Id == account.Id);

    if (exist == null && AddModal.EditContext.Validate())
    {
        Accounts.Add(account);
        AddModal.Close();
    };
}

The AddModalSubmitted function is responsible for adding a new account and is triggered when the OnSubmit event is triggered.

Finally add the following code to the file Home.razor.

@page "/"
@inherits HomeComponent

<PageTitle>Home</PageTitle>

<div class="container mt-5 mx-auto">
    <div class="row">
        <div class="col pb-5">
            <h3>Accounts</h3>
            <table class="table">
                <thead><tr><th>Name</th><th>Email address</th><th>Age</th><th>Expire date</th><th>#</th></tr></thead>
                <tbody>
                    @foreach (var account in Accounts)
                    {
                        <tr>
                            <td>@account.Name</td>
                            <td>@account.Email</td>
                            <td>@account.Age</td>
                            <td>@account.ExpireDate.ToShortDateString()</td>
                            <td>
                                <button class="btn btn-warning px-3" @onclick="()=>EditModal.Show(account)">
                                    Edit Account
                                </button>
                            </td>
                        </tr>
                    }
                </tbody>
            </table>
            <button class="btn btn-success mt-3 px-3" @onclick="()=>AddModal.Show(AccountModel)">
                Create Account
            </button>
        </div>
    </div>
</div>

<DncModal TItem="Account"
          @ref="EditModal"
          OnSubmit="EditModalSubmitted"
          OnShow="EditModalDisplayed"
          Size="Medium">

    <HeaderTemplate>
        <h5>Edit <b>@context.Name</b> Account</h5>
    </HeaderTemplate>

    <BodyTemplate>
        <DataAnnotationsValidator />
        <div class="mb-3">
            <label for="Name" class="form-label">Name</label>
            <InputText class="form-control" id="Name" @bind-Value="context.Name" />
            <ValidationMessage For="() => context.Name"></ValidationMessage>
        </div>
        <div class="mb-3">
            <label for="Email" class="form-label">Email address</label>
            <InputText class="form-control" id="Email" @bind-Value="context.Email" placeholder="name@example.com" />
            <ValidationMessage For="() => context.Email"></ValidationMessage>
        </div>
        <div class="mb-3">
            <label for="Age" class="form-label">Age</label>
            <InputNumber class="form-control" id="Age" @bind-Value="context.Age"/>
            <ValidationMessage For="() => context.Age"></ValidationMessage>
        </div>
        <div class="mb-3">
            <label for="ExpireDate" class="form-label">Expire Date</label>
            <InputDate class="form-control" id="ExpireDate" @bind-Value="context.ExpireDate" />
        </div>
    </BodyTemplate>

    <FooterTemplate>
        <button class="btn btn-secondary px-3" @onclick="()=>EditModal.Close()">Cancel</button>
        <button class="btn btn-warning  px-3" type="submit">Update the Account</button>
    </FooterTemplate>
</DncModal>

<DncModal TItem="Account"
          @ref="AddModal"
          OnSubmit="AddModalSubmitted"
          OnShow="AddModalDisplayed"
          Size="Larg">

    <HeaderTemplate>
        <h5>Create a new Account</h5>
    </HeaderTemplate>

    <BodyTemplate>
        <DataAnnotationsValidator />
        <div class="mb-3">
            <label for="Name" class="form-label">Name</label>
            <InputText class="form-control" id="Name" @bind-Value="AccountModel.Name" />
            <ValidationMessage For="() => context.Name"></ValidationMessage>
        </div>
        <div class="mb-3">
            <label for="Email" class="form-label">Email address</label>
            <InputText class="form-control" id="Email" @bind-Value="AccountModel.Email" placeholder="name@example.com" />
            <ValidationMessage For="() => AccountModel.Email"></ValidationMessage>
        </div>
        <div class="mb-3">
            <label for="Age" class="form-label">Age</label>
            <InputNumber class="form-control" id="Age" @bind-Value="AccountModel.Age" />
            <ValidationMessage For="() => AccountModel.Age"></ValidationMessage>
        </div>
        <div class="mb-3">
            <label for="ExpireDate" class="form-label">Expire Date</label>
            <InputDate class="form-control" id="ExpireDate" @bind-Value="AccountModel.ExpireDate" />
        </div>
    </BodyTemplate>

    <FooterTemplate>
        <button class="btn btn-secondary px-3" @onclick="()=>AddModal.Close()">Cancel</button>
        <button class="btn btn-success px-3" type="submit">Create Account</button>
    </FooterTemplate>
</DncModal>

We have defined a DncModal and specified the methods to be triggered when the onSubmit and OnShow events are triggered, and added the HTML markup in the HeaderTemplate and BodyTemplate with validation.


In addition, we need a reference to the AddModal Blazor Modal component using @ref , which is used as a variable in the code to display and close the Modal.
The Size is optional, you can set the modal size to Small, Medium (Default), Larg or ExtraLarg, as you see in the following code snippets.

<DncModal TItem="Account"
          @ref="AddModal"
          OnSubmit="AddModalSubmitted"
          OnShow="AddModalDisplayed"
          Size="Larg">
 <button class="btn btn-success mt-3 px-3" @onclick="()=>AddModal.Show(AccountModel)">
     Create Account
 </button>

Set the Dnc.Modal.WebApp as Startup project and run the application.

Blazor modal component in action

Add a new account and update an account with the Blazor Modal Component.

Add a new account with blazor modal component
Update an account with blazor modal component

Conclusion

In this post, we created a Blazor Modal component that you can use in your various projects. We started by creating a Blazor class library and then added the Blazor modal dialog component code and markup to finally take a look at the Blazor Modal dialog component.

The code for the reusable Blazor Modal component can be found Here

Also read https://dotnetcoder.com/create-json-localization-service-blazor-server/

Author

Exit mobile version