Hierarchical checklist in ASP.NET using Entity Framework and Razor models. Stripped of all unnecessary elements like CSS, JavaScript. You may need to check if you save the entries when clicking on the form and not together with the complete form but the exercise is to show a hierarchical structure.
Introduction
In this tip, you will see a short example to create a web checklist in ASP.NET with hierarchical structure (Checklist -> has Captions -> Have Entries).
Background
Based on a recent project, I wanted to post an example for those who need an inspiration for a similar task. The PJ is using EF Core, ASP.NET Core .NET 6.
The Model
public class Checklist
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public string Notes { get; set; }
public virtual ChecklistType ChecklistType { get; set; }
public virtual List<ChecklistCaption> ChecklistCaptions { get; set; }
}
public class ChecklistCaption
{
[Key]
public int CaptionId { get; set; }
public string CaptionName { get; set; }
public string HtmlId { get; set; }
public int SortOrder { get; set; }
public List<ChecklistEntry> Entries { get; set; }
}
public class ChecklistEntry
{
[Key]
public int EntryId { get; set; }
public string Name { get; set; }
public int SortOrder { get; set; }
public string HtmlId { get; set; }
public int ResponsibleUserId { get; set; }
public string TaskName { get; set; }
public int HideField { get; set; }
public string Description { get; set; }
public string HelpLink { get; set; }
public int Radio { get; set; }
public string RadioValue1Name { get; set; }
public string RadioValue2Name { get; set; }
public string RadioValue3Name { get; set; }
public int RadioValue1 { get; set; }
public int RadioValue2 { get; set; }
public int RadioValue3 { get; set; }
public string InputText { get; set; }
}
You will see later that a negative Value of Radio will switch the control to an input field with InputText
as Text-Field-Container.
I'm using code first and dependency injection in EF. The DB is a local SQLEXpress.
Steps Needed to Enable Entity Framework to be Usable in the Controller
Startup.cs (add service):
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContextFactory<ChecklistContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("WebApiDatabase")));
}
DBContext
(to define DBSet):
public class ChecklistContext : DbContext
{
protected readonly DbContextOptions<ChecklistContext> _dbContextOptions;
public ChecklistContext(DbContextOptions<ChecklistContext> dbContextOptions)
: base(dbContextOptions)
{
_dbContextOptions = dbContextOptions;
}
public DbSet<Checklist> Checklists { get; set; }
}
Controller with the injection:
public class ChecklistController : Controller
{
private readonly IDbContextFactory<ChecklistContext> _contextFactory;
private readonly IConfiguration _configuration;
public ChecklistController(IDbContextFactory<ChecklistContext> contextFactory,
IConfiguration configuration)
{
_contextFactory = contextFactory;
_configuration = configuration;
}
public ActionResult Index()
{
return View("Index");
}
public ActionResult Edit(int? Id)
{
using (var context = _contextFactory.CreateDbContext())
{
var checklist = context.Checklists
.Where(x => x.Id == Id)
.Include(x => x.ChecklistCaptions).ThenInclude
(x => x.Entries)
.FirstOrDefault();
if (checklist == null)
{
return NotFound();
}
return View(checklist);
}
}
View:
@model ChecklistCore3.Models.Checklist
<script type="text/javascript">
$(document).ready(function () {
});
</script>
<h2>@Model.Title (Id: @Model.Id)</h2>
@using (Html.BeginForm("UpdateChecklist", "Checklist",
FormMethod.Post, new { @id = "myform" }))
{
@Html.AntiForgeryToken()
<fieldset>
<table class="table checklist-table">
@Html.HiddenFor(model => model.Id)
<tr>
<td>
<h3>
@Html.LabelFor(model => Model.Title)
</h3>
</td>
<td colspan="4">
<h3>
@Html.TextBoxFor(model => Model.Title, new { @class = "w800p" })
</h3>
</td>
</tr>
@{
for (int i = 0; i < Model.ChecklistCaptions.Count(); i++)
{
<tr id="caption-@Model.ChecklistCaptions[i].HtmlId">
<td class="checklist-caption" colspan="6">
<span>@Model.ChecklistCaptions[i].CaptionName</span>
@Html.HiddenFor(model =>
Model.ChecklistCaptions[i].CaptionId)
</td>
</tr>
for (int j = 0; j < Model.ChecklistCaptions[i].Entries.Count(); j++)
{
<tr class="checklist-row responsible-
@((Model.ChecklistCaptions[i].Entries[j].
ResponsibleUserId > 0 && ViewBag.CurrUserId==
Model.ChecklistCaptions[i].Entries[j].
ResponsibleUserId).ToString())"
id="@Model.ChecklistCaptions[i].Entries[j].HtmlId">
<td class="checklist-sortorder">
@Html.DisplayTextFor(model =>
Model.ChecklistCaptions[i].Entries[j].SortOrder)
</td>
<td class="checklist-taskname">
@Html.HiddenFor(model =>
Model.ChecklistCaptions[i].Entries[j].TaskName)
@Html.HiddenFor(model =>
Model.ChecklistCaptions[i].Entries[j].EntryId)
@Html.HiddenFor(model =>
Model.ChecklistCaptions[i].Entries[j].HideField,
new { @class = "HideField" })
@Html.DisplayTextFor(model =>
Model.ChecklistCaptions[i].Entries[j].TaskName)
</td>
<td class="checklist-description">
@Html.HiddenFor(model =>
Model.ChecklistCaptions[i].Entries[j].Description)
@Html.DisplayTextFor(model =>
Model.ChecklistCaptions[i].Entries[j].Description)
@if (!string.IsNullOrEmpty
(Model.ChecklistCaptions[i].Entries[j].HelpLink))
{
<a href="@Model.ChecklistCaptions[i].
Entries[j].HelpLink" target="_blank">->
Link<span class="glyphicon
glyphicon-question-sign"></span></a>
}
</td>
<td class="checklist-checkbox">
@if (Model.ChecklistCaptions[i].Entries[j].Radio >= 0)
{
<div class="editor-field-radio">
@if (!string.IsNullOrEmpty
(Model.ChecklistCaptions[i].Entries[j].
RadioValue1Name))
{
<label class="dp-check-block">
@Html.RadioButtonFor(model =>
Model.ChecklistCaptions[i].Entries[j].
Radio, Model.ChecklistCaptions[i].
Entries[j].RadioValue1)
<span class="big">
@Model.ChecklistCaptions[i].Entries[j].
RadioValue1Name</span></label>
}
@if (!string.IsNullOrEmpty
(Model.ChecklistCaptions[i].Entries[j].
RadioValue2Name))
{
<label class="dp-check-block">
@Html.RadioButtonFor(model =>
Model.ChecklistCaptions[i].Entries[j].
Radio, Model.ChecklistCaptions[i].
Entries[j].RadioValue2)
<span class="big">
@Model.ChecklistCaptions[i].
Entries[j].RadioValue2Name</span></label>
}
@if (!string.IsNullOrEmpty
(Model.ChecklistCaptions[i].Entries[j].
RadioValue3Name))
{
<label class="dp-check-block">
@Html.RadioButtonFor(model =>
Model.ChecklistCaptions[i].Entries[j].
Radio, Model.ChecklistCaptions[i].
Entries[j].RadioValue3)
<span class="big">
@Model.ChecklistCaptions[i].
Entries[j].RadioValue3Name</span>
</label>
}
</div>
}
else
{
<div class="editor-field-radio">
@Html.HiddenFor(model =>
Model.ChecklistCaptions[i].Entries[j].Radio)
@Html.EditorFor(model =>
Model.ChecklistCaptions[i].Entries[j].
InputText)
</div>
}
</td>
</tr>
}
}
}
<tr>
<td>
@Html.LabelFor(model => Model.Notes)
</td>
<td colspan="5">
@Html.TextAreaFor(model => Model.Notes,
new { rows = "5", @class = "checklist-notes-textarea" })
@Html.ValidationMessageFor(model => Model.Notes)
</td>
</tr>
<tr>
<td colspan="5">
<div class="submit-div-checklist" title="Save checklist">
<p>
<input id="submit-btn" type="submit" value="Save"
class="btn btn-success fright">
</p>
</div>
</td>
</tr>
</table>
</fieldset>
}
The View
Iterates through captions and entries to create the checklist.
Model.ChecklistCaptions[i].Entries[j].TaskName
The Model Binding is able to recreate the class based on that structure when submitting the form.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UpdateChecklist(Checklist checklist)
{
return View("Edit",checklist);
}
History
- 18th September, 2022: Initial version