Quick Start to ASP.NET Core Web API and Blazor : Learn, Build, Deploy — Develop modern web apps using ASP.NET Core Web API, Minimal API, Identity, EF Core, and Blazor


Build Master-Detail Pages in ASP.NET Core MVC — Part 4

In Part 2 and Part 3 of this series, we completed the implementation of the TeamsController and TeamMembersController, respectively. So far, each controller action has returned the Main view, supplying it with a MasterDetailViewModel object. In this part, we'll begin crafting the views and partials that bring the application's user interface to life.

The figure below illustrates how various components are organized within the Views folder.

As shown, the Views folder is divided into three subfolders: Teams, TeamMembers, and Shared.

The Teams folder includes three partial views: _ShowTeam, _InsertTeam, and _UpdateTeam. These are responsible for rendering a team in read-only, insert, and update modes, respectively.

The TeamMembers folder similarly contains _ShowTeamMember, _InsertTeamMember, and _UpdateTeamMember, each handling the display of a team member in the corresponding mode.

The Shared folder houses the Main view along with two partials: _Teams and _TeamMembers. All actions from both TeamsController and TeamMembersController return the Main view, which dynamically renders the _Teams and _TeamMembers partials as needed. These partials display the master and detail grids, forming the core of the UI layout.

Now that we've explored the structure of the application's UI, let's begin assembling its components step by step.

To start, create the Main.cshtml view in VS Code. This view will serve as the central layout, orchestrating the rendering of partials for teams and team members.

Then add the following markup to the Main.cshtml file.

@model MasterDetailViewModel
...
<body>
    <partial name="_Teams" model="@Model" />
    <br /><br />
    <partial name="_TeamMembers" model="@Model" />
    <br /><br />
</body>
</html>

Notice the highlighted code—the @model directive declares MasterDetailViewModel as the model for the Main view. This class was introduced earlier in the series and serves as the bridge between controller actions and the UI. Every action in TeamsController and TeamMembersController passes a MasterDetailViewModel instance to the Main view, ensuring consistent data binding across the application.

The Main view then renders the _Teams and _TeamMembers partials, positioning the master grid at the top and the detail grid below it. To pass data to these partials, we use the model attribute of the <partial> tag helper, supplying the shared MasterDetailViewModel instance.

The _Teams partial referenced in the previous markup is explained in the section below.

To begin, add a new partial view named _Teams.cshtml to the Shared folder. Once created, populate it with the necessary code and markup to render the master grid for teams.

@model MasterDetailViewModel
<h1>List of Teams</h1>

<form method="post">

    <input type="submit"
           value="Insert Team"
           asp-controller="Teams"
           asp-action="InsertEntry" />

    <br /><br />

    <table border="1" cellpadding="10">
        <tr>
            <th>Team ID</th>
            <th>Name</th>
            <th>Description</th>
            <th colspan="2">Actions</th>
        </tr>

        @foreach (var item in Model.Teams)
        {
            if (Model.SelectedTeam != null)
            {
                if (item.TeamID == 
Model.SelectedTeam.TeamID)
                {
                    @:<tr class="SelectedRow">
                }
                else
                {
                    @:<tr>
                }
                }
            else
            {
                @:<tr>
            }
                <td>@item.TeamID</td>
                <td>@item.Name</td>
                <td>@item.Description</td>
                <td>
                    <input type="submit"
                           value="Manage Team"
                           asp-controller="Teams"
                           asp-action="Select"
                           asp-route-teamid="@item.TeamID" />
                </td>
                <td>
                    <input type="submit"
                           value="Manage Members"
                           asp-controller="TeamMembers"
                           asp-action="List"
                           asp-route-teamid="@item.TeamID" />
                </td>
            @:</tr>
        }
    </table>
</form>
<br /><br />

@{
    if (Model.DataEntryTarget == DataEntryTargets.Teams)
    {
        if (Model.SelectedTeam != null)
        {
            if (Model.DataDisplayMode == 
DataDisplayModes.Read)
            {
                await Html.RenderPartialAsync
("_ShowTeam", Model.SelectedTeam);
            }
            if (Model.DataDisplayMode == 
DataDisplayModes.Update)
            {
                await Html.RenderPartialAsync
("_UpdateTeam", Model.SelectedTeam);
            }
        }
        if (Model.DataDisplayMode == DataDisplayModes.Insert)
        {
            await Html.RenderPartialAsync
("_InsertTeam", new Team());
        }
    }
}

The _Teams partial receives the same MasterDetailViewModel object that was passed to the Main view. Notice that the <form> tag helper doesn't specify a controller or action directly. This is intentional—since the action depends on which button the user clicks (Insert, Edit, Save, etc.), we configure the asp-controller and asp-action attributes on the individual submit buttons instead of the form element itself.

The _Teams partial renders the master grid as shown below:

This form includes three submit buttons: Insert Team, Manage Team, and Manage Members.

The Insert Team button triggers the InsertEntry() action of the TeamsController, initiating the process to add a new team. The Manage Team button calls the Select() action of the TeamsController, passing the selected TeamID as a route parameter to identify which team to manage.

The Manage Members button submits the form to the List() action of the TeamMembersController, also passing the TeamID so that the corresponding team members can be displayed in the detail grid.

Team records are rendered in a table by iterating over the Teams collection within the MasterDetailViewModel. As each row is generated, we compare its TeamID with the SelectedItem.TeamID. If they match, the row is visually highlighted to indicate selection; otherwise, it's rendered with the default styling.

Below the table and form, you'll find a @code block that displays the selected team's details—if any. This block uses the DataDisplayMode property to determine how the team should be presented. Based on its value, the appropriate partial view is rendered: _ShowTeam for read-only display, _InsertTeam for adding a new team, or _UpdateTeam for editing an existing one. We'll create these partials in the upcoming sections of this series. For instance, a team displayed in read-only mode uses the _ShowTeam partial like this:

With that, the _Teams partial is complete. The _TeamMembers partial follows a similar structure and is outlined in the next section.

@model MasterDetailViewModel
@{
    if (Model.DataEntryTarget == 
DataEntryTargets.TeamMembers)
    {
        if (Model.SelectedTeam != null)
        {
            <h2>List of Members : 
@Model.SelectedTeam.Name</h2>

            <form method="post">

                <input type="submit"
                       value="Insert Member"
                       asp-controller="TeamMembers"
                       asp-action="InsertEntry" 
                       asp-route-teamid=
"@Model.SelectedTeam.TeamID"/>

                <br /><br />

                      <table border="1" cellpadding="10">
                          <tr>
                              <th>Team ID</th>
                              <th>Member ID</th>
                              <th>Name</th>
                              <th>Email</th>
                              <th colspan="2">Actions</th>
                          </tr>

                          @foreach (var item in 
Model.SelectedTeam.Members)
                          {
                              if (Model.SelectedTeamMember != null)
                              {
                                  if (item.TeamMemberID == 
Model.SelectedTeamMember.TeamMemberID)
                                  {
                                      @:<tr class="SelectedRow">
                                    }
                                    else
                                    {
                                        @:<tr>
                                    }
                                }
                                else
                                {
                                    @:<tr>
                                }
                                  <td>@item.TeamID</td>
                                  <td>@item.TeamMemberID</td>
                                  <td>@item.Name</td>
                                  <td>@item.Email</td>
                                  <td>
                           <input type="submit"
                           value="Manage Member"
                           asp-controller="TeamMembers"
                           asp-action="Select"
                           asp-route-teamid="@item.TeamID"
                           asp-route-memberid="@item.TeamMemberID"/>
                                  </td>
                              @:</tr>
                          }
                      </table>
            </form>
        }


        if (Model.SelectedTeamMember != null)
        {
            if (Model.DataDisplayMode == 
DataDisplayModes.Read)
            {
                await Html.RenderPartialAsync
("_ShowTeamMember", Model.SelectedTeamMember);
            }
            if (Model.DataDisplayMode == 
DataDisplayModes.Update)
            {
                await Html.RenderPartialAsync
("_UpdateTeamMember", Model.SelectedTeamMember);
            }
        }
        if (Model.DataDisplayMode == 
DataDisplayModes.Insert)
        {
            await Html.RenderPartialAsync
("_InsertTeamMember", new TeamMember() 
{ TeamID = Model.SelectedTeam.TeamID });
        }
    }
}

The _TeamMembers partial renders the detail grid as shown below:

The form includes two submit buttons: Insert Member and Manage Member. Clicking Insert Member submits the form to the InsertEntry() action of the TeamMembersController, along with the TeamID route parameter to associate the new member with the correct team. Clicking Manage Member submits the form to the Select() action of the same controller, passing both TeamID and MemberID as route parameters. This allows the application to identify and manage a specific team member within the selected team.

Since this is a detail grid, we iterate over the SelectedTeam.Members collection and display each team member in a table. Just like in the master grid, a selection marker highlights the currently selected member, helping users visually track their interactions.

At the bottom of the partial, you'll notice a series of if statements. These conditionally render one of three partial views—_ShowTeamMember, _InsertTeamMember, or _UpdateTeamMember—based on the DataDisplayMode of the SelectedTeam. These partials determine how the selected member is displayed: read-only, insert, or update mode. We'll explore these partials in detail in the next part of this series. For example, a team member shown in read-only mode is rendered using the _ShowTeamMember partial like this:

This wraps up the _TeamMembers partial. In the next installment of this series, we'll dive deeper into the partial views within the Teams and TeamMembers folders—exploring how each one contributes to the dynamic rendering of team and member data in various modes.

That's all for now—may your code carry clarity, and your craft reflect quiet devotion. With a breath of gratitude, I lay my pen down in peace.


Author : Bipin Joshi
Bipin Joshi is an independent software consultant and trainer, specializing in Microsoft web development technologies. Having embraced the yogic way of life, he also mentors select individuals in Ajapa Gayatri and allied meditative practices. Blending the disciplines of code and consciousness, he has been meditating, programming, writing, and teaching for over 30 years. As a prolific author, he shares his insights on both software development and yogic wisdom through his websites.

Posted On : 15 September 2025