Gaurav
Posted on August 26, 2024
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 :
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; } = "";
}
}
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>
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>
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>
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>
_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>
_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>
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");
}
}
}
Default User
User with Child Nodes
Final View
That's all for today.
And also, share your favourite web dev resources to help the beginners here!
Posted on August 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.