In this chapter, we will continue discussing the tag helpers. We will also add a new feature in our application and give it the ability to edit the details of an existing employee. We will start by adding a link on the side of each employee that will go to an Edit action on the HomeController.
@model HomePageViewModel @{ ViewBag.Title = "Home"; } <h1>Welcome!</h1> <table> @foreach (var employee in Model.Employees) { <tr> <td>@employee.Name</td> <td> <a asp-controller = "Home" asp-action = "Details" asp-routeid = "@employee.Id">Details</a> <a asp-controller = "Home" asp-action = "Edit" asp-routeid = "@employee.Id">Edit</a> </td> </tr> } </table>
We don't have the Edit action yet, but we will need an employee ID that we can edit. So let us first create a new view by right-clicking on the Views →Home folder and select Add → New Items.
In the middle pane, select the MVC View Page; call the page Edit.cshtml. Now, click on the Add button.
Add the following code in the Edit.cshtml file.
@model Employee @{ ViewBag.Title = $"Edit {Model.Name}"; } <h1>Edit @Model.Name</h1> <form asp-action="Edit" method="post"> <div> <label asp-for = "Name"></label> <input asp-for = "Name" /> <span asp-validation-for = "Name"></span> </div> <div> <input type = "submit" value = "Save" /> </div> </form>
For the title of this page, we can say that we want to edit and then provide the employee name.
The dollar sign in front of Edit will allow the runtime to replace Model.Name with a value that is in that property like employee name.
Inside the form tag, we can use tag helpers like asp-action and asp-controller. so that when the user submits this form it goes directly to a specific controller action.
In this case, we want to go to the Edit action on the same controller and we want to explicitly say that for the method on this form, it should be using an HttpPost.
The default method for a form is a GET, and we do not want to edit an employee using a GET operation.
In the label tag, we have used asp-for tag helper which says that this is a label for the Name property of the model. This tag helper can set up the Html.For attribute to have the correct value and to set the inner text of this label so that it actually displays what we want, like employee name.
Let us go to the HomeController class and add Edit action that returns the view that gives the user a form to edit an employee and then we will need a second Edit action that will respond to an HttpPost as shown below.
[HttpGet] public IActionResult Edit(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id); if (model == null) { return RedirectToAction("Index"); } return View(model); }
First, we need an edit action that will respond to a GET request. It will take an employee ID. The code here will be similar to the code that we have in the Details action. We will first extract the data of the employee that the user wants to edit. We also need to make sure that the employee actually exists. If it doesn't exist, we will redirect the user back to the Index view. But when an employee exists, we will render the Edit view.
We also need to respond to the HttpPost that the form will send.
Let us add a new class in the HomeController.cs file as shown in the following program.
public class EmployeeEditViewModel { [Required, MaxLength(80)] public string Name { get; set; } }
In the Edit Action which will respond to the HttpPost will take an EmployeeEditViewModel, but not an employee itself, because we only want to capture items that are in form in the Edit.cshtml file.
The following is the implementation of the Edit action.
[HttpPost] public IActionResult Edit(int id, EmployeeEditViewModel input) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var employee = sqlData.Get(id); if (employee != null && ModelState.IsValid) { employee.Name = input.Name; context.SaveChanges(); return RedirectToAction("Details", new { id = employee.Id }); } return View(employee); }
The edit form should always be delivered from an URL that has an ID in the URL according to our routing rules, something like /home/edit/1.
The form is always going to post back to that same URL, /home/edit/1.
The MVC framework will be able to pull that ID out of the URL and pass it as a parameter.
We always need to check if the ModelState is valid and also make sure that this employee is in the database and it is not null before we perform an update operation in the database.
If none of that is true, we will return a view and allow the user to try again. Although in a real application with concurrent users, if the employee is null, it could be because the employee details were deleted by someone.
If that employee doesn't exist, tell the user that the employee doesn't exist.
Otherwise, check the ModelState. If the ModelState is invalid, then return a view. This allows to fix the edit and make the ModelState valid.
Copy the name from the Input view model to the employee retrieved from the database and save the changes. The SaveChagnes() method is going to flush all those changes to the database.
The following is the complete implementation of the HomeController.
using Microsoft.AspNet.Mvc; using FirstAppDemo.ViewModels; using FirstAppDemo.Services; using FirstAppDemo.Entities; using FirstAppDemo.Models; using System.Collections.Generic; using System.Linq; using System.ComponentModel.DataAnnotations; namespace FirstAppDemo.Controllers { public class HomeController : Controller { public ViewResult Index() { var model = new HomePageViewModel(); using (var context = new FirstAppDemoDbContext()) { SQLEmployeeData sqlData = new SQLEmployeeData(context); model.Employees = sqlData.GetAll(); } return View(model); } public IActionResult Details(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id) if (model == null) { return RedirectToAction("Index"); } return View(model); } [HttpGet] public IActionResult Edit(int id) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var model = sqlData.Get(id); if (model == null) { return RedirectToAction("Index"); } return View(model); } [HttpPost] public IActionResult Edit(int id, EmployeeEditViewModel input) { var context = new FirstAppDemoDbContext(); SQLEmployeeData sqlData = new SQLEmployeeData(context); var employee = sqlData.Get(id); if (employee != null && ModelState.IsValid) { employee.Name = input.Name; context.SaveChanges(); return RedirectToAction("Details", new { id = employee.Id }); } return View(employee); } } public class SQLEmployeeData { private FirstAppDemoDbContext _context { get; set; } public SQLEmployeeData(FirstAppDemoDbContext context) { _context = context; } public void Add(Employee emp) { _context.Add(emp); _context.SaveChanges(); } public Employee Get(int ID) { return _context.Employees.FirstOrDefault(e => e.Id == ID); } public IEnumerable<Employee> GetAll() { return _context.Employees.ToList<Employee>(); } } public class HomePageViewModel { public IEnumerable<Employee> Employees { get; set; } } public class EmployeeEditViewModel { [Required, MaxLength(80)] public string Name { get; set; } } }
Let us compile the program and run the application.
We now have an Edit link available; let us edit the details of Josh by clicking on the Edit link.
Let us change the name to Josh Groban.
Click the Save button.
You can see that the name has been changed to Josh Groban as in the above screenshot. Let us now click on the Home link.
On the home page, you will now see the updated name.