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!!