Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 208 additions & 0 deletions SpeakingInBitsWeb/Controllers/CoursesController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using SpeakingInBitsWeb.Data;
using SpeakingInBitsWeb.Models;

namespace SpeakingInBitsWeb.Controllers;

[Authorize(Roles = Roles.Instructor)]
public class CoursesController : Controller
{
private readonly ApplicationDbContext _context;
private readonly UserManager<ApplicationUser> _userManager;

public CoursesController(ApplicationDbContext context, UserManager<ApplicationUser> userManager)
{
_context = context;
_userManager = userManager;
}

// GET: Courses
public async Task<IActionResult> Index()
{
string? userId = _userManager.GetUserId(User);
var courses = await _context.Courses
.Where(c => c.CourseInstructor != null && c.CourseInstructor.Id == userId)
.ToListAsync();
return View(courses);
}

// GET: Courses/Create
public IActionResult Create()
{
return View();
}

// POST: Courses/Create
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create(Course course)
{
string? userId = _userManager.GetUserId(User);
Instructor? instructor = await _context.Users.OfType<Instructor>()
.FirstOrDefaultAsync(i => i.Id == userId);

if (instructor != null)
{
course.CourseInstructor = instructor;
}

// Remove CourseInstructor from ModelState since it's being set here programmatically
// It must be removed because it's happening after model binding and validation
ModelState.Remove(nameof(Course.CourseInstructor));

if (ModelState.IsValid)
{
_context.Add(course);
await _context.SaveChangesAsync();
Comment on lines +49 to +65
return RedirectToAction(nameof(Index));
}
return View(course);
}

// GET: Courses/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}



// Fetch the course from the database, including the instructor
var courseToUpdate = await _context.Courses
.Include(c => c.CourseInstructor)
.FirstOrDefaultAsync(c => c.Id == id);

if (courseToUpdate == null)
{
return NotFound();
}

// Get current user ID to check ownership
string? userId = _userManager.GetUserId(User);
if (courseToUpdate.CourseInstructor == null || courseToUpdate.CourseInstructor.Id != userId)
{
return Forbid();
}

return View(courseToUpdate);
}

// POST: Courses/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, Course course)
{
if (id != course.Id)
{
return NotFound();
}

// Remove CourseInstructor from ModelState since we're not updating it
ModelState.Remove(nameof(Course.CourseInstructor));

if (ModelState.IsValid)
{
// Get current user ID
string? userId = _userManager.GetUserId(User);

// Fetch the course from the database, including the instructor
var courseToUpdate = await _context.Courses
.Include(c => c.CourseInstructor)
.FirstOrDefaultAsync(c => c.Id == id);

if (courseToUpdate == null)
{
return NotFound();
}

// Check ownership
if (courseToUpdate.CourseInstructor == null || courseToUpdate.CourseInstructor.Id != userId)
{
return Forbid();
}

// Update allowed properties only (do not update instructor)
courseToUpdate.Title = course.Title;
courseToUpdate.Description = course.Description;
Comment thread
JoeProgrammer88 marked this conversation as resolved.
courseToUpdate.CourseCode = course.CourseCode;

await _context.SaveChangesAsync();

return RedirectToAction(nameof(Index));
}
return View(course);
}

// GET: Courses/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}

// Get current user ID
string? userId = _userManager.GetUserId(User);

// Fetch the course from the database, including the instructor
var courseToDelete = await _context.Courses
.Include(c => c.CourseInstructor)
.FirstOrDefaultAsync(c => c.Id == id);

if (courseToDelete == null)
{
return NotFound();
}

// Check ownership
if (courseToDelete.CourseInstructor == null || courseToDelete.CourseInstructor.Id != userId)
{
return Forbid();
}

return View(courseToDelete);
}

// POST: Courses/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
// Get current user ID
string? userId = _userManager.GetUserId(User);

// Fetch the course from the database, including the instructor
var courseToDelete = await _context.Courses
.Include(c => c.CourseInstructor)
.FirstOrDefaultAsync(c => c.Id == id);

if (courseToDelete == null)
{
return NotFound();
}

// Check ownership
if (courseToDelete.CourseInstructor == null || courseToDelete.CourseInstructor.Id != userId)
{
return Forbid();
}

_context.Courses.Remove(courseToDelete);
await _context.SaveChangesAsync();

return RedirectToAction(nameof(Index));
}
}
17 changes: 17 additions & 0 deletions SpeakingInBitsWeb/Models/Roles.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace SpeakingInBitsWeb.Models;

/// <summary>
/// Defines constant values for application roles.
/// </summary>
public static class Roles
{
/// <summary>
/// Role name for instructors who can manage courses.
/// </summary>
public const string Instructor = "Instructor";

/// <summary>
/// Role name for students who can enroll in courses.
/// </summary>
public const string Student = "Student";
}
6 changes: 3 additions & 3 deletions SpeakingInBitsWeb/Models/SeedData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public static class SeedData
/// <param name="roleManager">The role manager service.</param>
public static async Task CreateRolesAsync(RoleManager<IdentityRole> roleManager)
{
string[] roles = ["Instructor", "Student"];
string[] roles = [Roles.Instructor, Roles.Student];

foreach (var role in roles)
{
Expand All @@ -35,7 +35,7 @@ public static async Task CreateDefaultUserAsync(UserManager<ApplicationUser> use

if (defaultUser == null)
{
ApplicationUser newUser = new()
Instructor newUser = new()
{
UserName = "DefaultInstructor",
Email = defaultEmail,
Expand All @@ -49,7 +49,7 @@ public static async Task CreateDefaultUserAsync(UserManager<ApplicationUser> use

if (result.Succeeded)
{
await userManager.AddToRoleAsync(newUser, "Instructor");
await userManager.AddToRoleAsync(newUser, Roles.Instructor);
}
}
}
Expand Down
28 changes: 20 additions & 8 deletions SpeakingInBitsWeb/SpeakingInBitsWeb.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
Expand All @@ -8,13 +8,25 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.0-rc.1.25451.107" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0-rc.1.25451.107" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="10.0.0-rc.1.25451.107" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0-rc.1.25451.107" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.0-rc.1.25451.107" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.0-rc.1.25451.107" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.0-rc.1.25458.5" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="10.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="10.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="10.0.2" />
<PackageReference Include="Microsoft.Build" Version="17.14.28">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Build.Tasks.Core" Version="17.14.28">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<Folder Include="Services\" />
</ItemGroup>

</Project>
43 changes: 43 additions & 0 deletions SpeakingInBitsWeb/Views/Courses/Create.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
@model SpeakingInBitsWeb.Models.Course

@{
ViewData["Title"] = "Create";
}

<h1>Create</h1>

<h4>Course</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="CourseCode" class="control-label"></label>
<input asp-for="CourseCode" class="form-control" />
<span asp-validation-for="CourseCode" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Description" class="control-label"></label>
<input asp-for="Description" class="form-control" />
<span asp-validation-for="Description" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>

<div>
<a asp-action="Index">Back to List</a>
</div>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
39 changes: 39 additions & 0 deletions SpeakingInBitsWeb/Views/Courses/Delete.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
@model SpeakingInBitsWeb.Models.Course

@{
ViewData["Title"] = "Delete";
}

<h1>Delete</h1>

<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Course</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Title)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Title)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.CourseCode)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.CourseCode)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Description)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Description)
</dd>
</dl>

<form asp-action="Delete">
<input type="hidden" asp-for="Id" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>
Loading
Loading