Family Tree View in .NET MVC

Gaurav - Aug 26 - - Dev Community

A couple of days ago, a candidate came in for an interview at the company where I work. He was given a task to create a dynamic family tree chart in .NET MVC, where each parent node would have two child nodes. The challenge was that when logging in with a child node, that node should move to the top of the tree as the new parent.

Watching him attempt the task reminded me of my own interview here, where I was given the same assignment. I remember how challenging it was to not only understand the logic but also to implement it visually in a tree-like structure. Unfortunately, the candidate struggled with creating the view, even though he had a good grasp of the underlying logic.

I realized that this is a common challenge, so I decided to write a blog post to help others who might encounter this task in the future. Today, I’m going to guide you on how to create a family tree view in .NET MVC.

Project Structure :

Image description

Here, we're going to use Html.Partial to render our nodes and a for loop to iterate multiple times. By default, we should have a user called "admin" (in my case, but you can create a different user if you prefer).

First, let's connect the .NET project with the database. For that, use the following model. We’re going to use username as the main node, with leftmember and rightmember as their children. This structure will continue recursively, with each node having two child nodes. If a node has not yet been created, a button will be available to add it.

Model

namespace TASK_1._1.Models
{
    public class UserModel
    {
        [Key]
        public int userId { get; set; }
        [Required]
        public string firstName { get; set; }
        [Required]
        public string lastName { get; set; }
        [Required]
        public string userName { get; set; }
        [Required]
        public string password { get; set; }
        public string leftMember { get; set; } = "";
        public string rightMember { get; set; } = "";
    }
}
Enter fullscreen mode Exit fullscreen mode

Login View

<div class="align-content-center">
    <br /><br />
    <form action="Auth" method="post">

        <div class="container">
            <label for="uname"><b>Username</b></label>
            <input type="text" placeholder="Enter Username" name="username" asp-route-username="username" required> <br /><br />

            <label for="psw"><b>Password</b></label>
            <input type="password" placeholder="Enter Password" name="password" asp-route-password="password" required><br /><br />

            <button type="submit" asp-action="Auth">Login</button>
            <br /><br />
        </div>


    </form>

</div>
Enter fullscreen mode Exit fullscreen mode

Index Page

@inject TASK_1._1.Context.ApplicationDbContext context
@model List<UserModel> 
@{
    ViewData["Title"] = "Home Page"; 
}

<div class="text-center">
    @if (Model != null && Model.Count() > 0)
    {  
        <table border="1" class="text-center">  
            @foreach (var user in Model)
            { 
                @Html.Partial("_UserNodeView", user)
                break;
            }
        </table> 
    }
</div>  
Enter fullscreen mode Exit fullscreen mode

RegisterLeftMember

@model UserModel
@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
<div class="text-center">

    <form action="AddMemberLeft" method="post">

        <div class="container">

            <label for="uname"><b>First Name</b></label>
            <input type="text" placeholder="Enter First Name" asp-for="firstName" required> <br /><br />

            <label for="uname"><b>Last Name</b></label>
            <input type="text" placeholder="Enter Last Name" asp-for="lastName" required> <br /><br />

            <label for="uname"><b>Username</b></label>
            <input type="text" placeholder="Enter Username" asp-for="userName" required> <br /><br />

            <label for="psw"><b>Password</b></label>
            <input type="password" placeholder="Enter Password" asp-for="password" required><br /><br />

            <button type="submit" asp-action="AddMemberLeft">Create</button>
            <br /><br />
        </div>

    </form>

</div>
Enter fullscreen mode Exit fullscreen mode

RegisterRightMember

@model UserModel
@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}
<div class="text-center">

    <form action="AddMemberRight" method="post">

        <div class="container">

            <label for="uname"><b>First Name</b></label>
            <input type="text" placeholder="Enter First Name" asp-for="firstName" required> <br /><br />

            <label for="uname"><b>Last Name</b></label>
            <input type="text" placeholder="Enter Last Name" asp-for="lastName" required> <br /><br />

            <label for="uname"><b>Username</b></label>
            <input type="text" placeholder="Enter Username" asp-for="userName" required> <br /><br />

            <label for="psw"><b>Password</b></label>
            <input type="password" placeholder="Enter Password" asp-for="password" required><br /><br />

            <button type="submit" asp-action="AddMemberRight">Create</button>
            <br /><br />
        </div>

    </form>

</div>
Enter fullscreen mode Exit fullscreen mode

_Layout.cshtml

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - TASK_1._1</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/TASK_1._1.styles.css" asp-append-version="true" />
</head>
<body>
    <header>

    </header>
    <div class="container">
        <main role="main" class="pb-3">
            @RenderBody()
        </main>
    </div>

    <script src="~/lib/jquery/dist/jquery.min.js"></script>
    <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
    <script src="~/js/site.js" asp-append-version="true"></script>
    @await RenderSectionAsync("Scripts", required: false)
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

_UserNodeView.cshtml

@inject TASK_1._1.Context.ApplicationDbContext context
@model UserModel
@*
    For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
*@
@{
}

<table border="1" class="text-center">
    <a> @Model.userName </a>
        <tr>
            <td>
            @if (Model.leftMember.Equals(""))
                {
                    <div>
                        <form asp-controller="Home" asp-action="AddMyLeftMember" method="post">
                        <button class="btn btn-primary" type="submit" asp-action="AddMyLeftMember" asp-route-userId="@Model.userId"> Left </button>
                        </form>
                    </div>
                }
                else
                {
                    var u = context.User.Where(n => n.userName == Model.leftMember).FirstOrDefault();
                    @Html.Partial("_UserNodeView", u)
                }
            </td>

            <td>
            @if (Model.rightMember.Equals(""))
                {
                    <div>
                        <form asp-controller="Home" asp-action="AddMyRightMember" method="post">
                        <button class="btn btn-primary" type="submit" asp-action="AddMyRightMember" asp-route-userId="@Model.userId"> Right </button>
                        </form>
                    </div>
                }
                else
                {
                    var u = context.User.Where(n => n.userName == Model.rightMember).FirstOrDefault();
                    @Html.Partial("_UserNodeView", u)
                }
            </td>
        </tr>

</table> 
Enter fullscreen mode Exit fullscreen mode

Controller

namespace TASK_1._1.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;
        private readonly ApplicationDbContext context;

        public HomeController(ILogger<HomeController> logger, ApplicationDbContext context)
        {
            _logger = logger;
            this.context = context;
        }

        public IActionResult Index()
        { 
            List<UserModel> users = context.User.ToList();

            string name = User.Identity.Name;

            if(name == null || name.Equals(""))
            {
                name = "admin";
            }

            List<UserModel> newUserList = users.Where(user => String.Compare(user.userName, User.Identity.Name) >= 0).ToList();
            return View(newUserList);

        }

        public async Task<IActionResult> addMyLeftMember(int userId)
        { 
            TempData["LeftMemberUserId"] = userId;
            return RedirectToAction("RegisterLeftMember");
        }

        public async Task<IActionResult> addMyRightMember(int userId)
        {
            TempData["RightMemberUserId"] = userId;
            return RedirectToAction("RegisterRightMember");
        }

        public async Task<IActionResult> AddMemberLeft(UserModel userModel)
        {

            UserModel usr = context.User.Where(n => n.userName == userModel.userName).FirstOrDefault();

            if(usr != null)
            {
                return Content("user already exist");
            }

            int userID = int.Parse(TempData["LeftMemberUserId"].ToString());
            var findingDaddy = context.User.Where(n => n.userId == userID).FirstOrDefault();


            findingDaddy.leftMember = userModel.userName;

            context.User.Add(userModel);
            await context.SaveChangesAsync();

            return RedirectToAction("Index");
        }

        public async Task<IActionResult> AddMemberRight(UserModel userModel)
        {

            UserModel usr = context.User.Where(n => n.userName == userModel.userName).FirstOrDefault();

            if (usr != null)
            {
                return Content("user already exist");
            }

            int userID = int.Parse(TempData["RightMemberUserId"].ToString());
            var findingDaddy = context.User.Where(n => n.userId == userID).FirstOrDefault();

            findingDaddy.rightMember = userModel.userName;

            context.User.Add(userModel);
            await context.SaveChangesAsync();

            return RedirectToAction("Index");
        }

        public IActionResult RegisterLeftMember()
        {
            return View();
        }

        public IActionResult RegisterRightMember()
        {
            return View();
        }

        public IActionResult Login()
        {
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }

        [HttpPost]
        public async Task<IActionResult> Auth(string username, string password)
        {

            var findingFeni = context.User.Where(n => n.userName == username && n.password == password).FirstOrDefault();

            if(findingFeni == null)
            { 
                return Content("user or password is incorrect");
            }

            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.Name, username)
            };

            var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);

            var authProperties = new AuthenticationProperties
            {

            };

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                new ClaimsPrincipal(claimsIdentity), authProperties);

            return RedirectToAction("Index");

        }

    }
}
Enter fullscreen mode Exit fullscreen mode

Default User

Image description

User with Child Nodes

Image description

Final View

Image description

That's all for today.

And also, share your favourite web dev resources to help the beginners here!

Connect with me:@ LinkedIn and checkout my Portfolio.

. . .
Terabox Video Player