Tap the power of breath, mantra, mudra, and dhyana.
Online course in Advanced Ajapa Japa and Shambhavi Mudra Meditation by Bipin Joshi.


Create master detail components in Blazor Server (Detail CRUD Components)

In the previous part of this article series you developed ListTeamMembers.razor component. Now you need to develop the remaining components namely ShowTeamMember.razor, InsertTeamMember.razor, and UpdateTeamMember.razor. You have already these empty component files in the Shared > TeamMembers folder.

The UI rendered by the ShowTeamMember component is shown below:

As you can see, the ShowTeamMember component displays a TeamMember and has Edit, Delete, and Cancel buttons.

To begin with, open the ShowTeamMember.razor component and inject AppDbContext as shown below:

@inject AppDbContext db

Then add the following two parameter properties in the @code block.

@code {

    [Parameter]
    public TeamMember SelectedTeamMember { get; set; }

    [Parameter]
    public EventCallback<TeamMemberEventArgs> 
DataButtonClick { get; set; }
}

The SelectedTeamMember properyy is set by the parent component and points to the Teammember currently selected for viewing. The DataButtonClick event callback is used to invoke the callback function when a DataButton is clicked from the component.

Next, add the RaiseButtonClicked() method as shown below:

public async Task RaiseButtonClicked(DataButton button)
{
    var args = new TeamMemberEventArgs()
        {
            Button = button,
            Item = SelectedTeamMember
        };

    await DataButtonClick.InvokeAsync(args);
}

The RaiseButtonClicked() method is called when you click on the Edit and Cancel buttons. Inside, it forms an object of TeamMemberEventArgs and wraps the DataButton and TeamMember in it. It then calls InvokeAsync() method of DataButtonClick event callback by passing the TeamMemberEventArgs to it.

Now add an event handler method that deeals with the delete operation.

private async Task OnDeleteClick()
{
    db.TeamMembers.Remove(SelectedTeamMember);
    db.SaveChanges();

    var args = new TeamMemberEventArgs()
        {
            Button = DataButton.Delete,
            Item = SelectedTeamMember
        };
    await DataButtonClick.InvokeAsync(args);
}

This code deletes the selected TeamMember from the database and  then invokes the DataButtonClick event callback.

This complete the @code block. Now let's add the necessary markup that renders the UI.

<h2>Edit Team Member : @SelectedTeamMember.Name</h2>

<table border="1" cellpadding="10">
    <tr>
        <td>Team ID :</td>
        <td>@SelectedTeamMember.TeamID</td>
    </tr>
    <tr>
        <td>Team Member ID :</td>
        <td>@SelectedTeamMember.TeamMemberID</td>
    </tr>
    <tr>
        <td>Name :</td>
        <td>@SelectedTeamMember.Name</td>
    </tr>
    <tr>
        <td>Email :</td>
        <td>@SelectedTeamMember.Email</td>
    </tr>
    <tr>
        <td colspan="2">
            <button @onclick="()=>RaiseButtonClicked
(DataButton.Edit)">Edit</button>
            <button @onclick="OnDeleteClick">Delete</button>
            <button @onclick="()=>RaiseButtonClicked
(DataButton.CancelReadMode)">Cancel</button>
            </td>
    </tr>
</table>

The UI displays the details of a selected TeamMember in a table. The three buttons at the bottom - Edit, Delete, and Cancel trigger the respective operations.

This completes the ShowTeamMember.razor component.

You also need to write the code in the parent OnDataButtonClick() method that loads the necessary components when these buttons are clicked.

So, open ListTeamMembers.razor component and add the following code to the OnDataButtonClick() method.

public void OnDataButtonClick(TeamMemberEventArgs args)
{
    if (args.Button == DataButton.CancelReadMode)
    {
        DynamicComponentType = typeof(DynamicPlaceHolder);
        DynamicComponentParams = null;
        SelectedItem = null;
    }

    if (args.Button == DataButton.Edit)
    {
        DynamicComponentType = typeof(UpdateTeamMember);
        DynamicComponentParams = new Dictionary<string, object>()
        {
            {"SelectedTeamMember",args.Item },
            {"DataButtonClick",DataButtonClickHandler}
        };
    }

    if (args.Button == DataButton.Delete)
    {
        DynamicComponentType = typeof(DynamicPlaceHolder);
        DynamicComponentParams = null;
    }
}

This code checks the DataButton clicked and accordingly loads an appropriate component in the <DynamicComponent>.

If the Cancel button from the ShowTeamMember component was clicked, you need to load the DynamicPlaceHolder. If the Edit button was clicked you need to load UpdateTeamMember component and if the Delete button was clicked you need to load DynamicPlaceHolder (since the record has been deleted).

Now let's move to the next component - InsertTeamMember.razor.

The InsertTeamMember looks like this when rendered in the browser:

Open the InsertTeamMember.razor componet and add the following code:

@inject AppDbContext db


@code {

    [Parameter]
    public Team SelectedTeam { get; set; }


    [Parameter]
    public EventCallback<TeamMemberEventArgs> 
DataButtonClick { get; set; }

    public TeamMember NewTeamMember 
{ get; set; } = new TeamMember();


    public async Task RaiseButtonClicked
(DataButton button)
    {
        var args = new TeamMemberEventArgs()
            {
                Button = button,
                Item = NewTeamMember
            };
        await DataButtonClick.InvokeAsync(args);
    }

    private async Task OnSaveClick()
    {
        NewTeamMember.TeamID = SelectedTeam.TeamID;
        db.TeamMembers.Add(NewTeamMember);
        db.SaveChanges();

        var args = new TeamMemberEventArgs()
            {
                Button = DataButton.Insert,
                Item = NewTeamMember
            };
        await DataButtonClick.InvokeAsync(args);
    }
}

This code should look familiar to you because you used it in earlier components also.

Take a look at the lines marked in bold letters from the OnSaveClick() method. That code simply adds a new TeamMember object to the TeamMembers DbSet and calls the SaveChanges() method.

The markup responsible for rendering the component's UI is shown below:

<h2>Insert New Team Member</h2>

<EditForm Model="NewTeamMember" OnValidSubmit="OnSaveClick">
    <DataAnnotationsValidator></DataAnnotationsValidator>

    <table border="0" cellpadding="10">
        <tr>
            <td class="right">
                <label for="FirstName">Name :</label>
            </td>
            <td>
                <InputText id="Name" 
@bind-Value="NewTeamMember.Name" />
                <ValidationMessage For="(() => 
NewTeamMember.Name)" />
            </td>
        </tr>
        <tr>
            <td class="right">
                <label for="Email">Email :</label>
            </td>
            <td>
                <InputText id="Email" 
@bind-Value="NewTeamMember.Email" />
                <ValidationMessage 
For="(() => NewTeamMember.Email)" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">Save</button>
                <button type="button" 
@onclick="()=>RaiseButtonClicked
(DataButton.CancelInsertMode)">Cancel</button>
            </td>
        </tr>
    </table>
    <ValidationSummary></ValidationSummary>
</EditForm>

Notice that OnValidSubmit calls the OnSaveClick() method upon form submission. The Cancel button calls the RaiseButtonClicked() method by passing DataButton.CancelInsertMode.

After adding this markup open ListTeamMembers.razor component again and modify the OnDataButtonClick() as shown below:

public void OnDataButtonClick(TeamMemberEventArgs args)
{
    // ===
    // code added earlier goes here
    // ===

    if (args.Button == DataButton.Insert)
    {
        DynamicComponentType = typeof(ShowTeamMember);
        DynamicComponentParams = new Dictionary<string, object>()
        {
            {"SelectedTeamMember",args.Item },
            {"DataButtonClick",DataButtonClickHandler}
        };
    }
    if (args.Button == DataButton.CancelInsertMode)
    {
        DynamicComponentType = typeof(DynamicPlaceHolder);
        DynamicComponentParams = null;
    }
}

If the Insert button was clicked you load ShowTeamMember component and if Cancel button was clicked you load DynamicPlaceHolder component.

This completes the InsertTeamMember.razor component.

Let's proceed to create the final component - UpdateTeamMember.razor.

This component looks like this when loaded in the browser:

Open UpdateTeamMember.razor file and add the following code in it:

@inject AppDbContext db


@code {

    [Parameter]
    public TeamMember SelectedTeamMember { get; set; }

    [Parameter]
    public EventCallback<TeamMemberEventArgs> 
DataButtonClick { get; set; }

    public async Task RaiseButtonClicked(DataButton button)
    {
        var args = new TeamMemberEventArgs()
            {
                Button = button,
                Item = SelectedTeamMember
            };
        await DataButtonClick.InvokeAsync(args);
    }

    private async Task OnSaveClick()
    {
        db.SaveChanges();

        var args = new TeamMemberEventArgs()
            {
                Button = DataButton.Update,
                Item = SelectedTeamMember
            };
        await DataButtonClick.InvokeAsync(args);
    }
}

Notice the OnSaveClick() method that saves the modifications to the database.

The markup responsible for displaying the edit form is shown below:

<h2>Edit Team Member : @SelectedTeamMember.Name</h2>

<EditForm Model="SelectedTeamMember" OnValidSubmit="OnSaveClick">
    <DataAnnotationsValidator></DataAnnotationsValidator>

    <table border="0" cellpadding="10">
        <tr>
            <td class="right">
                <label>Team ID :</label>
            </td>
            <td>
                @SelectedTeamMember.TeamID
            </td>
        </tr>
        <tr>
            <td class="right">
                <label>Team Member ID :</label>
            </td>
            <td>
                @SelectedTeamMember.TeamMemberID
            </td>
        </tr>
        <tr>
            <td class="right">
                <label for="Name">Name :</label>
            </td>
            <td>
                <InputText id="Name" 
@bind-Value="SelectedTeamMember.Name" />
                <ValidationMessage 
For="(() => SelectedTeamMember.Name)" />
            </td>
        </tr>
        <tr>
            <td class="right">
                <label for="Email">Email :</label>
            </td>
            <td>
                <InputText id="Description" 
@bind-Value="SelectedTeamMember.Email" />
                <ValidationMessage 
For="(() => SelectedTeamMember.Email)" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">Save</button>
                <button type="button" 
@onclick="()=>RaiseButtonClicked
(DataButton.CancelEditMode)">Cancel</button>
            </td>
        </tr>
    </table>
    <ValidationSummary></ValidationSummary>
</EditForm>

Notice that OnValidSubmit calls the OnSaveClick() method upon form submission. The Cancel button calls the RaiseButtonClicked() method by passing DataButton.CancelEditMode.

After adding this markup open ListTeamMembers.razor component again and modify the OnDataButtonClick() as shown below:

public void OnDataButtonClick(TeamMemberEventArgs args)
{
    // ===
    // code added earlier goes here
    // ===

    if (args.Button == DataButton.Update)
    {
        DynamicComponentType = typeof(ShowTeamMember);
        DynamicComponentParams = new Dictionary<string, object>()
        {
            {"SelectedTeamMember",args.Item },
            {"DataButtonClick",DataButtonClickHandler}
        };
    }


    if (args.Button == DataButton.CancelEditMode)
    {
        DynamicComponentType = typeof(ShowTeamMember);
        DynamicComponentParams = new Dictionary<string, object>()
        {
            {"SelectedTeamMember",args.Item },
            {"DataButtonClick",DataButtonClickHandler}
        };
    }

}

If the Update button was clicked you load ShowTeamMember component (after saving the changes) and if Cancel button was clicked you load ShowTeamMember component (no changes are saved).

This completes the UpdateTeamMember component. And it also completes the CRUD operations on the TeamMembers table.

Run the application and check whether all pieces of functionality work as expected.

That's it for now! Keep coding!!


Bipin Joshi is an independent software consultant and trainer by profession specializing in Microsoft web development technologies. Having embraced the Yoga way of life he is also a meditation teacher and spiritual guide to his students. He is a prolific author and writes regularly about software development and yoga on his websites. He is programming, meditating, writing, and teaching for over 27 years. To know more about his ASP.NET online courses go here. More details about his Ajapa Japa and Shambhavi Mudra online course are available here.

Posted On : 20 June 2022