#nullable disable
using APP.Models;
using APP.Services;
using CORE.APP.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
// Generated from Custom MVC Template.
namespace MVC.Controllers
{
public class UsersController : Controller
{
// Service injections:
private readonly IService<UserRequest, UserResponse> _userService;
private readonly IService<GroupRequest, GroupResponse> _groupService;
// For AJAX:
private readonly IService<CityRequest, CityResponse> _cityService;
private readonly IService<CountryRequest, CountryResponse> _countryService;
/* Can be uncommented and used for many to many relationships, "entity" may be replaced with the related entity name in the controller and views. */
private readonly IService<RoleRequest, RoleResponse> _RoleService;
public UsersController(
IService<UserRequest, UserResponse> userService
, IService<GroupRequest, GroupResponse> groupService
// For AJAX:
, IService<CityRequest, CityResponse> cityService
, IService<CountryRequest, CountryResponse> countryService
/* Can be uncommented and used for many to many relationships, "entity" may be replaced with the related entity name in the controller and views. */
, IService<RoleRequest, RoleResponse> RoleService
)
{
_userService = userService;
_groupService = groupService;
// For AJAX:
_cityService = cityService;
_countryService = countryService;
/* Can be uncommented and used for many to many relationships, "entity" may be replaced with the related entity name in the controller and views. */
_RoleService = RoleService;
}
private void SetViewData()
{
/*
ViewBag and ViewData are the same collection (dictionary).
They carry extra data other than the model from a controller action to its view, or between views.
*/
// Related items service logic to set ViewData (Id and Name parameters may need to be changed in the SelectList constructor according to the model):
ViewData["GroupId"] = new SelectList(_groupService.List(), "Id", "Title");
/* Can be uncommented and used for many to many relationships, "entity" may be replaced with the related entity name in the controller and views. */
ViewBag.RoleIds = new MultiSelectList(_RoleService.List(), "Id", "Name");
}
private void SetTempData(string message, string key = "Message")
{
/*
TempData is used to carry extra data to the redirected controller action's view.
*/
TempData[key] = message;
}
// GET: Users
// Way 1:
//[Authorize(Roles = "Admin,User")] // Only authenticated users with role Admin or User can execute this action.
// Way 2: since we have only 2 roles Admin and User, we can use Authorize to check auhenticated users without defining roles.
[Authorize] // Only authenticated users can execute this action.
public IActionResult Index()
{
// Get collection service logic:
var list = _userService.List();
return View(list); // return response collection as model to the Index view
}
/// <summary>
/// Regular users can only make operations on their own accounts.
/// </summary>
/// <param name="id">int</param>
/// <returns>bool</returns>
bool IsOwnAccount(int id) // private is default if not written
{
// getting the user ID value for the claim type "Id" from the user's claims and checking if it matches the provided id parameter
return id.ToString() == (User.Claims.SingleOrDefault(claim => claim.Type == "Id")?.Value ?? string.Empty);
}
// GET: Users/Details/5
[Authorize] // Only authenticated users can execute this action.
public IActionResult Details(int id)
{
// Check if the user is in Admin role or trying to make the operation on his/her own account.
if (!IsOwnAccount(id) && !User.IsInRole("Admin"))
{
SetTempData("You are not authorized for this operation!");
return RedirectToAction(nameof(Index));
}
// Get item service logic:
var item = _userService.Item(id);
return View(item); // return response item as model to the Details view
}
// GET: Users/Create
[Authorize(Roles = "Admin")] // Only authenticated users with role Admin can execute this action.
public IActionResult Create()
{
SetViewData(); // set ViewData dictionary to carry extra data other than the model to the view
// For AJAX: get all the countries for the Country dropdown list
ViewData["CountryId"] = new SelectList(_countryService.List(), "Id", "CountryName");
return View(); // return Create view with no model
}
// POST: Users/Create
[HttpPost, ValidateAntiForgeryToken]
[Authorize(Roles = "Admin")] // Only authenticated users with role Admin can execute this action.
public IActionResult Create(UserRequest user)
{
if (ModelState.IsValid) // check data annotation validation errors in the request
{
// Insert item service logic:
var response = _userService.Create(user);
if (response.IsSuccessful)
{
SetTempData(response.Message); // set TempData dictionary to carry the message to the redirected action's view
return RedirectToAction(nameof(Details), new { id = response.Id }); // redirect to Details action with id parameter as response.Id route value
}
ModelState.AddModelError("", response.Message); // to display service error message in the validation summary of the view
}
SetViewData(); // set ViewData dictionary to carry extra data other than the model to the view
// For AJAX: get all the countries for the Country dropdown list with country selected by user's CountryId
ViewData["CountryId"] = new SelectList(_countryService.List(), "Id", "CountryName", user.CountryId);
return View(user); // return request as model to the Create view
}
// GET: Users/Edit/5
[Authorize] // Only authenticated users can execute this action.
public IActionResult Edit(int id)
{
// Check if the user is in Admin role or trying to make the operation on his/her own account.
if (!IsOwnAccount(id) && !User.IsInRole("Admin"))
{
SetTempData("You are not authorized for this operation!");
return RedirectToAction(nameof(Index));
}
// Get item to edit service logic:
var item = _userService.Edit(id);
SetViewData(); // set ViewData dictionary to carry extra data other than the model to the view
// For AJAX: get all the countries for the Country dropdown list with country selected by item's CountryId
ViewData["CountryId"] = new SelectList(_countryService.List(), "Id", "CountryName", item.CountryId);
// For AJAX: get all the cities by item's CountryId for the City dropdown list with city selected by item's CityId
// Casting Way 1:
//var cityService = (CityService)_cityService;
// Casting Way 2:
var cityService = _cityService as CityService; // Cast to concrete CityService to access List by countryId method
// since injected IService instance does not have List by countryId method definition
ViewData["CityId"] = new SelectList(cityService.List(item.CountryId), "Id", "CityName", item.CityId);
return View(item); // return request as model to the Edit view
}
// POST: Users/Edit
[HttpPost, ValidateAntiForgeryToken]
[Authorize] // Only authenticated users can execute this action.
public IActionResult Edit(UserRequest user)
{
// Check if the user is in Admin role or trying to make the operation on his/her own account.
if (!IsOwnAccount(user.Id) && !User.IsInRole("Admin"))
{
SetTempData("You are not authorized for this operation!");
return RedirectToAction(nameof(Index));
}
// Because we don't get the values for the Score and RoleIds properties from the user request for users not in
// the Admin role, and Score and RoleIds are required, we must remove the ModelState error for the Score and
// RoleIds properties to prevent validation failure.
if (!User.IsInRole("Admin"))
{
ModelState.Remove(nameof(UserRequest.Score));
ModelState.Remove(nameof(UserRequest.RoleIds));
}
if (ModelState.IsValid) // check data annotation validation errors in the request
{
// Update item service logic:
var response = _userService.Update(user);
if (response.IsSuccessful)
{
SetTempData(response.Message); // set TempData dictionary to carry the message to the redirected action's view
return RedirectToAction(nameof(Details), new { id = response.Id }); // redirect to Details action with id parameter as response.Id route value
}
ModelState.AddModelError("", response.Message); // to display service error message in the validation summary of the view
}
SetViewData(); // set ViewData dictionary to carry extra data other than the model to the view
// For AJAX: get all the countries for the Country dropdown list with country selected by user's CountryId
ViewData["CountryId"] = new SelectList(_countryService.List(), "Id", "CountryName", user.CountryId);
// For AJAX: get all the cities by user's CountryId for the City dropdown list with city selected by user's CityId
var cityService = _cityService as CityService; // Cast to concrete CityService to access List by countryId method
// since injected IService instance does not have List by countryId method definition
ViewData["CityId"] = new SelectList(cityService.List(user.CountryId), "Id", "CityName", user.CityId);
return View(user); // return request as model to the Edit view
}
// GET: Users/Delete/5
[Authorize] // Only authenticated users can execute this action.
public IActionResult Delete(int id)
{
// Check if the user is in Admin role or trying to make the operation on his/her own account.
if (!IsOwnAccount(id) && !User.IsInRole("Admin"))
{
SetTempData("You are not authorized for this operation!");
return RedirectToAction(nameof(Index));
}
// Get item to delete service logic:
var item = _userService.Item(id);
return View(item); // return response item as model to the Delete view
}
// POST: Users/Delete
[HttpPost, ValidateAntiForgeryToken, ActionName("Delete")]
[Authorize] // Only authenticated users can execute this action.
public IActionResult DeleteConfirmed(int id)
{
// Check if the user is in Admin role or trying to make the operation on his/her own account.
if (!IsOwnAccount(id) && !User.IsInRole("Admin"))
{
SetTempData("You are not authorized for this operation!");
return RedirectToAction(nameof(Index));
}
// Delete item service logic:
var response = _userService.Delete(id);
SetTempData(response.Message); // set TempData dictionary to carry the message to the redirected action's view
// if the user deleted his/her own account, log out the user
if (IsOwnAccount(id))
return RedirectToAction(nameof(Logout));
return RedirectToAction(nameof(Index)); // redirect to the Index action
}
/// <summary>
/// Displays the login view for user authentication.
/// Route changed from GET: /Users/Login to GET: /Login for easier access.
/// </summary>
[Route("~/[action]")]
public IActionResult Login()
{
return View();
}
/// <summary>
/// Handles user login form submission.
/// POST: /Login
/// </summary>
/// <param name="request">The login request containing user credentials (UserName and Password).</param>
/// <returns>
/// Redirects to Home/Index on successful login; otherwise, redisplays the login view with
/// validation errors or authentication failure message.
/// </returns>
[HttpPost, ValidateAntiForgeryToken]
[Route("~/[action]")]
public async Task<IActionResult> Login(UserLoginRequest request)
{
if (ModelState.IsValid) // Validate input model (checks required fields and string lengths through data annotations)
{
// Casting Way 1:
//var userService = (UserService)_userService;
// Casting Way 2:
var userService = _userService as UserService; // Cast to concrete UserService to access Login method
// since injected IService instance does not have Login method definition
var response = await userService.Login(request); // Attempt to authenticate user
if (response.IsSuccessful)
return RedirectToAction("Index", "Home"); // On success, redirect to home page
ModelState.AddModelError("", response.Message); // On failure, show error message in the validation summary of the view
}
return View(); // Redisplay login view with validation errors or authentication failure message
}
/// <summary>
/// Logs out the current user and redirects to Login.
/// Route changed from GET: /Users/Logout to GET: /Logout for easier access.
/// </summary>
/// <remarks>This method terminates the user's authentication cookie.</remarks>
/// <returns>Redirects to Login.</returns>
[Route("~/[action]")]
public async Task<IActionResult> Logout()
{
var userService = _userService as UserService; // Cast to concrete UserService to access Logout method
// since injected IService instance does not have Logout method definition
await userService.Logout(); // Sign out the user
return RedirectToAction(nameof(Login)); // Redirect to the Login view through the Login action
}
/// <summary>
/// Displays the register view for user registration.
/// Route changed from GET: /Users/Register to GET: /Register for easier access.
/// </summary>
[Route("~/[action]")]
public IActionResult Register()
{
return View();
}
/// <summary>
/// Handles user register form submission.
/// POST: /Register
/// </summary>
/// <param name="request">The register request containing UserName, Password and ConfirmPassword.</param>
/// <returns>
/// Redirects to Login on successful registration; otherwise, redisplays the register view with validation errors
/// or registration failure message.
/// </returns>
[HttpPost, ValidateAntiForgeryToken, Route("~/[action]")]
public IActionResult Register(UserRegisterRequest request)
{
if (ModelState.IsValid) // Validate input model through data annotations
{
var userService = _userService as UserService; // Cast to concrete UserService to access Register method
// since injected IService instance does not have Register method definition
var response = userService.Register(request); // Attempt to register user
if (response.IsSuccessful)
return RedirectToAction(nameof(Login)); // On success, redirect to Login view through the Login action
ModelState.AddModelError("", response.Message); // On failure, show error message in the validation summary of the view
}
return View(request); // Redisplay register view with the model and validation errors or registration failure message
}
}
}