Advanced Ajapa Yoga Kriyas and Meditations for Software Developers : Tap the power of breath, mantra, mudra, and dhyana for improved focus, peace of mind, and blissful inner connection.


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

In the previous part of this article series you developed the List component that displays a list of Teams in a table. In this part you will create ShowTeam, InsertTeam, and UpdateTeam components to perform the CRUD operations on the Teams table.

The following figure shows how the ShowTeam component displays a Team in the browser:

Open ShowTeam.razor component added from the previous part and inject AppDbContext in it as shown below:

@inject AppDbContext db

In the @code block add two parameter properties called SelectedTeam and DataButtonClick. These properties are shown below:

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

[Parameter]
public Action<DataButton,Team> DataButtonClick { get; set; }

Recollect that ShowTeam is loaded dynamically by the parent List component. And the SelectedItem property is assigned a value by the parent based on user's selection in the table. The DataButtonClick is a callback function and it's also assigned by the ent List component.

When a user clicks on the Edit or Cancel button the DataButtonClick callback needs to be invoked. This is done in RaiseDataButtonClick() event handler method as shown below:

public void RaiseDataButtonClick(DataButton operation)
{
    DataButtonClick?.Invoke(operation,SelectedTeam);
}

The DataButtonClick callback is triggered using the Invoke() method. The RaiseDataButtonClick() is called when Edit or Cancel button is clicked.

Upon clicking the Delete button you need to delete the Team from the database. This is done in the click event handler of the Delete button as shown below:

private void OnDeleteClick()
{
    db.Teams.Remove(SelectedTeam);
    db.SaveChanges();
    DataButtonClick?.Invoke(DataButton.Delete,SelectedTeam);
}

As you can see, this code deletes the SelectedTeam from the database and also raises the DataButtonClick callback.

You could have combined the RaiseDataButtonClick() and OnDeleteClick() but I am keeping them separate here for the sake of easy understanding.

This completes the @code block. The following code shows the completed @code block for your quick checking:

@code {

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

    [Parameter]
    public Action<DataButton,Team> DataButtonClick { get; set; }

    public void RaiseDataButtonClick(DataButton operation)
    {
        DataButtonClick?.Invoke(operation,SelectedTeam);
    }

    private void OnDeleteClick()
    {
        db.Teams.Remove(SelectedTeam);
        db.SaveChanges();
        DataButtonClick?.Invoke(DataButton.Delete,SelectedTeam);
    }
}

Now let's add the markup responsible for showing the Team record and the buttons.

<h2>Edit Team : @SelectedTeam.Name</h2>

<table border="1" cellpadding="10">
    <tr>
        <td>Team ID :</td>
        <td>@SelectedTeam.TeamID</td>
    </tr>
    <tr>
        <td>Name :</td>
        <td>@SelectedTeam.Name</td>
    </tr>
    <tr>
        <td>Description :</td>
        <td>@SelectedTeam.Description</td>
    </tr>
    <tr>
        <td colspan="2">
            <button @onclick="()=>RaiseDataButtonClick
(DataButton.Edit)">Edit</button>
            <button @onclick="OnDeleteClick">Delete</button>
            <button @onclick="()=>RaiseDataButtonClick
(DataButton.CancelReadMode)">Cancel</button>
            </td>
    </tr>
</table>

This markup is quite straightforward. Notice the lines marked in bold letters. The Edit and Cancel buttons wire the onclick event handler to the RaiseDataButtonClick() method. They also pass the DataButton value of Edit and CancelReadMode to the handler. The Delete button onclick event calls the OnDeleteClick() method.

This complete the ShowTeam.razor component.

Next, open the ListTeams.razor component and write the following code in the OnDataButtonClick() method (currently it's empty).

public void OnDataButtonClick(DataButton button, 
Team item)
{
    if (button == DataButton.CancelReadMode)
    {
        DynamicComponentType = typeof(DynamicPlaceHolder);
        DynamicComponentParams = null;
        SelectedItem = null;
    }

    if (button == DataButton.Edit)
    {
        DynamicComponentType = typeof(UpdateTeam);
        DynamicComponentParams = new 
Dictionary<string, object>()
        {
            {"SelectedTeam",item },
            {"DataButtonClick",DataButtonClickHandler}
        };
    }

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

    Items = db.Teams.ToList();

    this.StateHasChanged();
}

Here, you need to decide the next dynamic component to be loaded depending on the button clicked in the ShowTeam component.

If the Cancel button was clicked you simply load the empty DynamicPlaceHolder component. This way the ShowList will be replaced by DynamicPlaceHolder. Since clicking on the Cancel buttons means that the current selection is cancelled, you also set SelectedItem to null.

If the Edit button was clicked, you need to load the UpdateTeam component that displays the selected Team for modification. So, ShowList will be replaced by UpdateTeam component.

Similarly, if the Delete button was clicked you need to load DynamicPlaceHolder because the record has already been deleted and there is nothing to show.

Now let's turn our attention to InsertTeam.razor component.

The InsertTeam component is loaded upon clicking the Insert button from the List component and is shown below:

Open InsertTeam.razor and write the following code in it.

@inject AppDbContext db


@code {

    [Parameter]
    public Action<DataButton,Team> 
DataButtonClick { get; set; }

    public Team NewTeam { get; set; } = new Team();


    public void RaiseDataButtonClick
(DataButton operation)
    {
        DataButtonClick?.Invoke(operation,NewTeam);
    }

    private void OnSaveClick()
    {
        db.Teams.Add(NewTeam);
        db.SaveChanges();
        DataButtonClick?.Invoke
(DataButton.Insert,NewTeam);
    }
}

This code should look familiar to you because it's similar to ShowTeam component.

Since it's an insert operation the parent component doesn't supply any Team to InsertTeam component. The NewTeam property is a new Team entity. The RaiseDataButtonClick() is called when you click on the Cancel button and raises the DataButtonClick callback.

The OnSaveClick() is called when you click on the Save button.This event handler adds the new Team into the database and raises the DataButtonClick callback.

The following markup is responsible for displaying the UI of the InsertTeam component.

<h2>Insert New Team</h2>

<EditForm Model="NewTeam" 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="NewTeam.Name" />
                <ValidationMessage For="(() => NewTeam.Name)" />
            </td>
        </tr>
        <tr>
            <td class="right">
                <label for="Description">Description :</label>
            </td>
            <td>
                <InputText id="Description" 
@bind-Value="NewTeam.Description" />
                <ValidationMessage 
For="(() => NewTeam.Description)" />
            </td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">Save</button>
                <button type="button" @onclick="()=>
RaiseDataButtonClick(DataButton.CancelInsertMode)">Cancel</button>
            </td>
        </tr>
    </table>
    <ValidationSummary></ValidationSummary>
</EditForm>

To render the data entry form you use Blazor's EditForm component. You also enable data annotation based validations using DataAnnotationsValidator component. The validation errors are displayed using ValidationMessage and ValidationSummary components. The textboxes accepting Team Name and Description are displayed using InputText component.

Upon clicking the Save button the OnSaveClick() event handler is called and clicking the Cancel button triggers the RaiseDataButtonClick() method. Note that you pass Insert and CancelInsertMode in these methods. I won't go in the details of Blazor's form components here. You can learn more about them in the Microsoft's official documentation.

This completes InsertTeam.razor component.

Let's write the code necessary to deal with Insert and CancelInsertMode DataButton values.

Open ListTeams.razor and go to OnDataButtonClick() callback method. Add the following code to this method:

if (button == DataButton.CancelInsertMode)
{
    DynamicComponentType = typeof(DynamicPlaceHolder);
    DynamicComponentParams = null;
}

And

if (button == DataButton.Insert)
{
    DynamicComponentType = typeof(ShowTeam);
    DynamicComponentParams = new Dictionary<string, object>()
    {
        {"SelectedTeam",item },
        {"DataButtonClick",DataButtonClickHandler}
    };
}

When a user clicks the Cancel button the InsertTeam component is replaced with DynamicPlaceHolder component. Similarly, when a new Team is successfully added, InsertTeam is replaced with ShowTeam component.

Now let's complete the UpdateTeam.razor component.

When you click on the Edit button in the ShowTeam component, the UpdateTeam component gets loaded and looks like this:

Open UpdateTeam.razor and add the following code:

@inject AppDbContext db


@code {

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

    [Parameter]
    public Action<DataButton,Team> 
DataButtonClick { get; set; }

    public void RaiseDataButtonClick(DataButton operation)
    {
        DataButtonClick?.Invoke(operation,SelectedTeam);
    }

    private void OnSaveClick()
    {
        db.SaveChanges();
        DataButtonClick?.Invoke
(DataButton.Update,SelectedTeam);
    }
}

This code is similar to ShowTeam.razor and InsertTeam.razor components.

The RaiseDataButtonClick() sends CancelEditMode to the DataButtonClick callback whereas OnSaveClick() sends Update to the callback.

The markup responsible for rendering the update form is shown below:

<h2>Edit Team : @SelectedTeam.Name</h2>

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

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

This time model for the EditForm is SelectedTeam object and OnValidClick points to the OnSaveClick() handler function. The Cancel button triggers the RaiseDataButtonClick() method.

This completes the UpdateTeam.razor component.

Now go back to the ListTeams.razor and add this code to the OnDataButtonClick() callback function:

if (button == DataButton.CancelEditMode)
{
    DynamicComponentType = typeof(ShowTeam);
    DynamicComponentParams = new Dictionary<string, object>()
    {
        {"SelectedTeam",item },
        {"DataButtonClick",DataButtonClickHandler}
    };
}

And

if (button == DataButton.Update)
{
    DynamicComponentType = typeof(ShowTeam);
    DynamicComponentParams = new Dictionary<string, object>()
    {
        {"SelectedTeam",item },
        {"DataButtonClick",DataButtonClickHandler}
    };
}

When you click on the Cancel button in the UpdateTeam component, the callback function replaces UpdateTeam with ShowTeam to reflect the read-only display of the selected Team. When you click on the Save button on the UpdateTeam.razor component, the callback function loads the ShowTeam component and will reflect the changes made by the user. Although these pieces of code are identical, it's better to keep them separate in case you need to plug-in some different logic for the Cancel and Save buttons.

This completes all the master components.

Open the MasterDetailContainer.razor component that resides in the Pages folder (you added it in the previous part of this article series). And add the following code:

@page "/masterdetail"

<h1>Master Detail Demo</h1>

@code {

}
<ListTeams></ListTeams>

As you can see the MasterDetailContainer component is associated with /masterdetail route. And it houses the ListTeams component you created earlier.

You can now run the application and navigate to /masterdetail.

You should see the Teams listing as before. The following figure shows Team 1 in read-only mode:

And the following figure shows Team 1 in edit mode:

Finally, the following figure shows the modified Team details reflected in the table:

In this part of this article series you completed the master CRUD components. In the next part of this series you will begin developing the detail components. 

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 : 19 May 2022







Advanced Ajapa Yoga Kriyas and Meditations for Software Developers : Tap the power of breath, mantra, mudra, and dhyana for improved focus, peace of mind, and blissful inner connection.