using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using PhoneToolMX.Data; using PhoneToolMX.Helpers; using PhoneToolMX.Models; using PhoneToolMX.Models.ViewModels; using System.Security.Authentication; using System.Security.Claims; namespace PhoneToolMX.Controllers { [Authorize] [Route("[controller]")] public abstract class BaseController : Controller where T: OwnedBase, IModel where TViewModel : IViewModel, new() { protected private readonly PTMXContext _context; private readonly UserManager _userManager; protected BaseController(UserManager mgr, PTMXContext ctx) { _context = ctx; _userManager = mgr; } protected private virtual Task PreForm(TViewModel vm) { throw new NotImplementedException(); } #region Helper methods /// /// Gets the "friendly" name for an owned model object. /// /// The model object to inspect /// The friendly name, or its ID if none was defined private static string GetFriendlyName(IViewModel vm) { if (vm is not TViewModel model) return vm.Id.ToString(); return typeof(TViewModel).GetProperties().Where(pi => { var headers = pi.GetCustomAttributes(typeof(HeaderAttribute), false); return headers.Length != 0 && ((HeaderAttribute)headers[0]).Primary; }).Select(pi => pi.GetValue(model)?.ToString()).FirstOrDefault() ?? model.Id.ToString(); } private static string GetFriendlyName(IOwnedModel model) { var vm = new TViewModel().FromEntity(model); return GetFriendlyName(vm); } protected private async Task CurrentUser() { var claim = HttpContext.User.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier)); if (claim == null) { throw new InvalidCredentialException("fuck this noise, nameid claim missing"); } var user = await _userManager.FindByIdAsync(claim.Value); return user; } private async Task FormView(TViewModel vm) { await PreForm(vm); return View("_Form", vm); } private void SetMessage(FormMessage msg) { TempData["Message"] = msg.ToString(); } private void SetMessage(FormMessageType type, string msg) { SetMessage(new FormMessage { Type = type, Message = msg, }); } private async Task ValidationError(TViewModel vm) { var plural = ModelState.ErrorCount > 1 ? "s" : string.Empty; SetMessage( FormMessageType.Error, $"Error{plural} occurred in validation. Make the changes required and click Submit to try again." ); return await FormView(vm); } #endregion #region Create [HttpGet("New")] public async Task New() { ViewData["Action"] = "add"; ViewData["Title"] = $"Add new {typeof(T).Name.ToLower()}"; return await FormView(default); } [HttpPost("New")] public async Task NewPost(TViewModel vm) { ViewData["Title"] = $"New {typeof(T).Name}"; if (!ModelState.IsValid) return await ValidationError(vm); if (vm.ToEntity(_context) is not T model) throw new InvalidOperationException($"{typeof(TViewModel).FullName}.ToEntity() somehow produced something other than a {typeof(T).FullName}"); var entity = await _context.AddOwnable(await CurrentUser(), model); await _context.SaveChangesAsync(); SetMessage( FormMessageType.Success, $"{typeof(T).Name} {GetFriendlyName(new TViewModel().FromEntity(entity.Entity))} was created." ); return RedirectToAction("Edit", new { id = entity.Entity.Id }); } #endregion #region Read [HttpGet] public async Task Index() { ViewData["Title"] = $"My {typeof(T).Name}s"; return View("Index", _context .GetOwned(await CurrentUser()) .Select(m => (TViewModel)new TViewModel().FromEntity(m)) .ToList()); } #endregion #region Update [HttpGet("Edit")] public async Task Edit(int id) { ViewData["Action"] = "update"; var model = _context.GetOwned(await CurrentUser()).FirstOrDefault(o => o.Id == id); if (model == null) return NotFound(); ViewData["Title"] = $"Editing {typeof(T).Name.ToLower()} {GetFriendlyName(new TViewModel().FromEntity(model))}"; return await FormView((TViewModel)new TViewModel().FromEntity(model)); } [HttpPost("Edit")] public async Task EditPost(TViewModel vm) { if (vm?.Id == null) { return BadRequest(); } if (!ModelState.IsValid) return await ValidationError(vm); // merge VM's changes with DbModel var currentModel = _context.GetEntityById(vm.Id); if (vm.ToEntity(_context, currentModel) is not T entity) throw new InvalidOperationException($"{typeof(TViewModel).FullName}.ToEntity() somehow produced something other than a {typeof(T).FullName}"); currentModel.Commit(entity); // and commit back _context.Set().Update(currentModel); await _context.SaveChangesAsync(); SetMessage( FormMessageType.Success, $"{typeof(T).Name} {GetFriendlyName(vm)} was updated." ); return RedirectToAction("Edit", new { id = entity.Id }); } #endregion #region Delete [HttpGet("Delete")] public async Task Delete(int id) { var model = _context.GetEntityById(id); if (model?.IsOwnedBy(await CurrentUser()) != true) { return NotFound(); } ViewData["Title"] = $"Delete {GetFriendlyName(model)}"; TempData["ModelDelete"] = model.Id; return View("DeleteConfirm", model); } [HttpPost("Delete")] public async Task PostDelete(int id) { if ((int?)TempData["ModelDelete"] != id) { return BadRequest(); } var model = _context.GetEntityById(id); if (model?.IsOwnedBy(await CurrentUser()) != true) { return NotFound(); } _context.Set().Remove(model); await _context.SaveChangesAsync(); SetMessage( FormMessageType.Success, $"{typeof(T).Name} {GetFriendlyName(new TViewModel().FromEntity(model))} was successfully deleted." ); return RedirectToAction("Index"); } #endregion } }