ASP.NET Core 2 MVC无JS实现任务工作/休息时长统计优化咨询
Great question—let's build a clean, server-side-only solution for your ASP.NET Core 2 MVC task tracking that avoids JavaScript entirely, while being more robust than a basic implementation. Here's a streamlined approach that follows MVC best practices:
1. Model Setup
First, define a TimeEntry model to track work sessions. This will link to your existing Task model and store start/end times:
public class TimeEntry { public int Id { get; set; } public int TaskId { get; set; } public DateTime Start { get; set; } public DateTime? End { get; set; } // Nullable since a session might be active // Navigation property to your Task model public Task Task { get; set; } }
Don't forget to add this DbSet to your database context:
public DbSet<TimeEntry> TimeEntries { get; set; }
2. Controller Actions
Update your TaskController with two new POST actions to handle starting work and ending a session (break). We'll add validation to prevent edge cases like duplicate active sessions:
public class TaskController : Controller { private readonly YourDbContext _context; public TaskController(YourDbContext context) { _context = context; } // Existing Details action (updated to pass latest active session to view) public async Task<IActionResult> Details(int? id) { if (id == null) return NotFound(); var task = await _context.Tasks.FirstOrDefaultAsync(m => m.Id == id); if (task == null) return NotFound(); // Fetch latest unfinished session for UI state handling var latestActiveSession = await _context.TimeEntries .Where(te => te.TaskId == id && te.End == null) .OrderByDescending(te => te.Start) .FirstOrDefaultAsync(); ViewData["LatestActiveSession"] = latestActiveSession; return View(task); } // Handle "Start Work" button submission [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> StartWork(int taskId) { // Prevent starting a new session if one is already active var hasActiveSession = await _context.TimeEntries .AnyAsync(te => te.TaskId == taskId && te.End == null); if (hasActiveSession) { TempData["AlertMessage"] = "You already have an active work session for this task!"; return RedirectToAction(nameof(Details), new { id = taskId }); } var newSession = new TimeEntry { TaskId = taskId, Start = DateTime.Now }; _context.TimeEntries.Add(newSession); await _context.SaveChangesAsync(); TempData["AlertMessage"] = "Work session started successfully!"; return RedirectToAction(nameof(Details), new { id = taskId }); } // Handle "Take Break" button submission (ends active session) [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> EndWork(int taskId) { // Find the most recent active session var latestSession = await _context.TimeEntries .Where(te => te.TaskId == taskId && te.End == null) .OrderByDescending(te => te.Start) .FirstOrDefaultAsync(); if (latestSession == null) { TempData["AlertMessage"] = "No active work session found to end!"; return RedirectToAction(nameof(Details), new { id = taskId }); } latestSession.End = DateTime.Now; _context.Update(latestSession); await _context.SaveChangesAsync(); TempData["AlertMessage"] = "Work session ended (break started) successfully!"; return RedirectToAction(nameof(Details), new { id = taskId }); } }
3. View Implementation (/Task/Details/{id})
Add two server-side forms to your Details view—one for each button. We'll also add UI state handling to disable buttons when they shouldn't be clickable, and show user feedback via TempData:
@model YourNamespace.Models.Task @{ ViewData["Title"] = "Task Details"; var hasActiveSession = ViewData["LatestActiveSession"] != null; } <h1>@Model.Name</h1> <!-- Display task details here --> <div class="card mb-3"> <div class="card-body"> <dl class="row"> <dt class="col-sm-3">Description:</dt> <dd class="col-sm-9">@Model.Description</dd> <!-- Add other task fields as needed --> </dl> </div> </div> <!-- Display alert messages --> @if (TempData["AlertMessage"] != null) { <div class="alert alert-info mb-3">@TempData["AlertMessage"]</div> } <!-- Start Work Form --> <form asp-action="StartWork" method="post" class="d-inline"> <input type="hidden" name="taskId" value="@Model.Id" /> <button type="submit" class="btn btn-success" @(hasActiveSession ? "disabled" : "")> Start Work </button> </form> <!-- Take Break Form --> <form asp-action="EndWork" method="post" class="d-inline ms-2"> <input type="hidden" name="taskId" value="@Model.Id" /> <button type="submit" class="btn btn-warning" @(!hasActiveSession ? "disabled" : "")> Take Break (End Session) </button> </form> <div class="mt-3"> <a asp-action="Edit" asp-route-id="@Model.Id" class="btn btn-secondary">Edit Task</a> <a asp-action="Index" class="btn btn-secondary ms-2">Back to Task List</a> </div>
Optional Enhancements
- Session Duration Display: Add a section to the view showing the duration of the active session (if any) by calculating the difference between
DateTime.Nowand the session'sStarttime. - History Table: Include a table in the Details view that lists all past work sessions for the task, showing start/end times and total duration.
- Role-Based Authorization: Add
[Authorize]attributes to the actions if only specific users should track time for tasks.
内容的提问来源于stack exchange,提问作者Ruby Harris

