During this session, we are focusing on the implementation of CRUD operations in Controller and View in our ASP.NET Core MVC Web App. As I have already mentioned in session 48 CRUD stands for Create, Read, Update and Delete which are four basic operations that should be implemented in each persistent storage application. Also during that session, we developed our CRUD repositories for both static and SQL Server data storage. Hence, now we are developing these operations on the Controllers and Views of our Web App.
Implementing Create Operation in Controller and View in ASP.NET Core MVC
Earlier we discussed Action Methods, HttpsGet, and HttpsPost methods in detail. Now for implementing Create operation we need to create two Action Methods in the respective Controller. Then we should decorate one of them to [HttpGet] and the other one to [HttpPost]. Action Method with HttpGet decoration will return the form that we need for collecting data. Also inside this Action Method, we can get and then pass the required data (same as dropdown items) to the View. Then, after posting the data from View to Controller, the Action Method with HttpPost decoration is invoked. Then, this Action Method can check the validity of the posted data. Thereupon, if the posted data passed the validation check we can store the data in the storage media via Repository; otherwise, we can return the view with validation errors.
Create Operation in ASP.NET Core MVC
- Create an Action Method with HttpGet decoration inside the respective Controller.
- Pass and return the required data to the respective View.
- Post the collected data inside the View to the Controller.
- Check the posted data validity and store if they are valid.
- Return the View with the validation error if the posted data are not valid.
Now we are going to implement this operation on our Web App which we are developing during this tutorial. Thus, We implement CRUD operation for the Cost entity and you can do the same for the other entities as a practice. Hence, we create required Action Methods inside CostController, but before this one, we need to create an Interface and Repository for the Category entity. Because we need to get and show the list of categories inside the Cost Create Form.
ICategoryRepositoty:
1 2 3 4 5 6 7 8 9 |
public interface ICategoryRepository { IEnumerable<Category> GetAllCategories(); } } |
SQLServerCategoryRepository:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class SQLServerCategoryRepository : ICategoryRepository { private readonly WebAppDBContext webAppDBContext; public SQLServerCategoryRepository(WebAppDBContext _webAppDBContext) { webAppDBContext = _webAppDBContext; } public IEnumerable<Category> GetAllCategories() { return webAppDBContext.Categories; } } |
Also, we need to add this service inside the Startup class, ConfigureService Method:
1 2 3 4 5 |
services.AddScoped<ICategoryRepository, SQLServerCategoryRepository>(); |
Moreover, we need to do a correction in the relationship that we implemented during Session 47. Because we make a one-to-one relation between Cost and Category while it should be a one-to-many relationship; Therefore, we need to modify the Category entity as shown below and run Add-Migration and Update-Database commands.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class Category { [Key] public int ID { get; set; } [Required] public string CategoryName { get; set; } public string Description { get; set; } [Required] public CategoryActiveOptions Active { get; set; } public virtual List<Cost> Costs { get; set; } } |
Now we can add both ICostRepository and ICategoryRepository to the CostController.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class CostController : Controller { private readonly ICostRepository costRepository; private readonly ICategoryRepository categoryRepository; public CostController(ICostRepository _costRepository, ICategoryRepository _categoryRepository) { costRepository = _costRepository; categoryRepository = _categoryRepository; } } |
And two final steps, first adding Create Action method with HttpGet decoration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[HttpGet] public IActionResult Create() { var Categories = categoryRepository.GetAllCategories(); List<SelectListItem> CatList = new(); foreach (var category in Categories) { CatList.Add(new SelectListItem(category.CategoryName, category.ID.ToString())); } ViewBag.Categories = CatList; return View(); } |
and Finally, adding Create Action Method with HttpPost decoration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
public IActionResult Create(CreateCostViewModel model) { if (ModelState.IsValid) { Cost cost = new() { Amount = model.Amount, Comment = model.Comment, RegisteredDate = model.RegisteredDate, CategoryID = model.CategoryID, PaymentMethod = model.PaymentMethod }; costRepository.Create(cost); return RedirectToAction("Index"); } var Categories = categoryRepository.GetAllCategories(); List<SelectListItem> CatList = new(); foreach (var category in Categories) { CatList.Add(new SelectListItem(category.CategoryName, category.ID.ToString())); } ViewBag.Categories = CatList; return View(); } |
Now, we need to add Create View. For this I use the code that I explained during Session 44 and Session 29.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
@model CreateCostViewModel @{ ViewBag.Title = "Create Cost"; } <form asp-controller="Cost" asp-action="Create" method="post" class="mt-5 offset-3 col-6 border border-primary pt-2 pb-2 px-2 py-2"> <div> <label asp-for="Amount" class="form-label"></label> <input asp-for="Amount" class="form-control" /> <span asp-validation-for="Amount" class="text-danger"></span> </div> <div> <label asp-for="RegisteredDate" class="form-label"></label> <input asp-for="RegisteredDate" class="form-control" /> <span asp-validation-for="RegisteredDate" class="text-danger"></span> </div> <div> <label asp-for="Comment" class="form-label"></label> <input asp-for="Comment" class="form-control" /> <span asp-validation-for="Comment" class="text-danger"></span> </div> <div> <label asp-for="CategoryID" class="form-label"></label> <select asp-for=CategoryID asp-items="@ViewBag.Categories" class="form-select"></select> </div> <div> <label asp-for="PaymentMethod" class="form-label"></label> <select asp-for="PaymentMethod" class="form-select" asp-items="Html.GetEnumSelectList<PaymentMethods>()"></select> </div> <div><button type="submit" class="btn btn-primary mt-2">Create</button></div> </form> |
Display List of All Stored Records in ASP.NET Core MVC
We already implemented this practice during Session 22, yet due to implementing all the CRUD operations, we implement it again. Thus we need an HTTPGet Action Method. This Action Method collects the records via the Repository and passes the data to the respective View. Then, the View renders all the records with the foreach loop.
Read Operation and List of Records in ASP.NET Core MVC
- Create an Action Method with HttpGet decoration inside the respective Controller.
- Collect the respective records through the Repository.
- Pass the collected data to the View
- Render all the records in the View with the foreach loop.
Now, we start again with our ASP.NET Core MVC web app. Thus, we need to add Index Action Method and decorate it with HttpGet. Also, we need to collect all the Cost records with the CostRepository and pass them to the View.
1 2 3 4 5 6 7 8 9 10 |
[HttpGet] public IActionResult Index() { var costs = costRepository.GetAllCost(); return View(costs); } |
Thereupon, we can render all the records inside the View with the foreach loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
@model IEnumerable<Cost> @{ ViewBag.Title = "Cost List"; } <div class="row"> <div class=col-3> <a asp-controller="cost" asp-action="create" class="btn btn-primary mx-2 my-2">Create New Cost</a> </div> </div> <table class="table table-dark"> <thead> <tr> <th>ID</th> <th>Amount</th> <th>Category</th> <th>Comment</th> <th>Payment Method</th> <th colspan="3" class="text-center">Actions</th> </tr> </thead> <tbody> @foreach (var cost in Model) { <tr> <td>@cost.ID</td> <td>@cost.Amount</td> <td>@cost.CategoryID</td> <td>@cost.Comment</td> <td>@cost.PaymentMethod</td> <form asp-controller="cost" asp-action="delete" asp-route-id="@cost.ID" method="post"> <td><a class="btn btn-primary d-block" asp-controller="cost" asp-action="detail" asp-route-id="@cost.ID">View</a></td> <td><a class="btn btn-info btn-block d-block" asp-controller="cost" asp-action="update" asp-route-id="@cost.ID">Edit</a></td> <td><button class="btn btn-danger btn-block d-block" type="submit">Delete</button></td> </form> </tr> } </tbody> </table> |
Furthermore, We need to modify the Web App main menu to lead us to the Cost/Index Action Method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container-fluid"> <a class="navbar-brand" asp-controller="home" asp-action="index"> <img src="~/images/cost.png" alt="" width="82" height="51"> </a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarSupportedContent"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <a class="nav-link" aria-current="page" asp-controller="home" asp-action="index">Home</a> </li> <li class="nav-item"> <a class="nav-link" asp-controller="cost" asp-action="index">Cost List</a> </li> </ul> </div> </div> </nav> |
Implementing Read Operation in Controller and View in ASP.NET Core MVC
For this practice, we need again an Action Method with HttpGet decoration and input parameter. Inside the Action Method, via the Repository, we get the respective data that we receive its index from the input parameter. Then we pass the data to the View to display the detail of the selected record.
Read Operation and Display the Detail of a Record in ASP.NET Core MVC
- Create an Action Method with HttpGet decoration inside the respective Controller with an input parameter.
- Get the respective record through the Repository.
- Pass the detail to the View
- Display the detail in the View.
Now we need to implement this function on our Web App. Thus, we need to create “Detail” Action Method inside the CostController. Also, as you can see in the Index View code, we pass the ID of the respective Cost record to the Cost/Detail action method with asp-route-id.
1 2 3 4 5 6 7 8 9 10 |
[HttpGet] public IActionResult Detail(int id) { var cost = costRepository.GetCostByID(id); return View(cost); } |
Thereupon, we create Detail View and use Bootstrap classes for styling. Worth mentioning that, we discussed the Bootstrap classes in Session 27.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@model Cost @{ ViewBag.Title = "Cost Detail"; } <div class="row"> <div class="col-lg-4 offset-lg-4 col-md-6 offset-md-3 col-12"> <div class="card"> <div class="card-header"> Detail Cost ID @Model.ID </div> <div class="card-body"> <p class="card-text">Amount: @Model.Amount</p> <p class="card-text">Category: @Model.CategoryID</p> <p class="card-text">Comment: @Model.Comment</p> <p class="card-text">Payment Method: @Model.PaymentMethod</p> <a class="btn btn-primary" asp-controller="cost" asp-action="index">Cost List</a> </div> </div> </div> </div> |
Implementing Update Operation in Controller and View in ASP.NET Core MVC
For Update a record, first we need an HttpGet Action Method. This Action method handles collecting current record data and passes the data and other required data to the View. Then we can modify the data inside the View and Post the modified data to the Controller again. This time the Action Method with HttpPost decoration handles the process. the Action Method checked the data validation and submit the data for updating the storage media if the data is valid. Otherwise, return the view with the validation errors.
Update Operation in ASP.NET Core MVC
- Create an Action Method with HttpGet decoration inside the respective Controller.
- Pass and return the required data to the respective View.
- Post the updated data to the Controller.
- Check the posted data validity and store if they are valid in HttpPost Action Method.
- Return the View with the validation error if the posted data are not valid.
Next, we implement this function on our Web App as well. As you can see in Index View, we call the Cost/Update Action Method for this purpose. Also, we passed the ID of the Cost with asp-route-id to the Update action method with HttpGet decoration.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[HttpGet] public IActionResult Update(int id) { var cost = costRepository.GetCostByID(id); var Categories = categoryRepository.GetAllCategories(); List<SelectListItem> CatList = new(); foreach (var category in Categories) { CatList.Add(new SelectListItem(category.CategoryName, category.ID.ToString())); } ViewBag.Categories = CatList; return View(cost); } |
After that, we add the Update action method which handles the posted data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[HttpPost] public IActionResult Update(Cost model) { if (ModelState.IsValid) { costRepository.Update(model); return RedirectToAction("Index"); } var cost = costRepository.GetCostByID(model.ID); var Categories = categoryRepository.GetAllCategories(); List<SelectListItem> CatList = new(); foreach (var category in Categories) { CatList.Add(new SelectListItem(category.CategoryName, category.ID.ToString())); } ViewBag.Categories = CatList; return View(cost); } |
Also, you can check the Update View code below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
@model Cost @{ ViewBag.Title = "Create Cost"; } <form asp-controller="Cost" asp-action="Update" method="post" class="mt-5 offset-3 col-6 border border-primary pt-2 pb-2 px-2 py-2"> <input hidden asp-for="ID"/> <div> <label asp-for="Amount" class="form-label"></label> <input asp-for="Amount" class="form-control" /> <span asp-validation-for="Amount" class="text-danger"></span> </div> <div> <label asp-for="RegisteredDate" class="form-label"></label> <input asp-for="RegisteredDate" class="form-control" /> <span asp-validation-for="RegisteredDate" class="text-danger"></span> </div> <div> <label asp-for="Comment" class="form-label"></label> <input asp-for="Comment" class="form-control" /> <span asp-validation-for="Comment" class="text-danger"></span> </div> <div> <label asp-for="CategoryID" class="form-label"></label> <select asp-for=CategoryID asp-items="@ViewBag.Categories" class="form-select"></select> </div> <div> <label asp-for="PaymentMethod" class="form-label"></label> <select asp-for="PaymentMethod" class="form-select" asp-items="Html.GetEnumSelectList<PaymentMethods>()"></select> </div> <div class="row mt-2"> <div class="col-3 d-grid"> <button type="submit" class="btn btn-primary">Edit</button> </div> <div class="col-3 d-grid"> <a asp-action="index" class="btn btn-info">Cost List</a> </div> </div> </form> |
Implementing Delete Operation in Controller and View in ASP.NET Core MVC
We can implement this function with HttpGet and HttpPost methods. But due to security reasons, we recommend implementing this function with the HttpPost method. Because the main difference between HttpPost and HttpGet method is the Cross-Site Request Forgery (CSRF) attack. Thus, if we implement this function with the HttpGet method, the end-users or hackers can delete the records maliciously. This behavior can be implemented through URL Query String or even malicious links.
Why deleting a record with HTTPGet is not a secure way?
Because the main difference between HttpPost and HttpGet method is the Cross-Site Request Forgery (CSRF) attack. Thus, if we implement this function with the HttpGet method, the end-users or hackers can delete the records maliciously. This behavior can be implemented through URL Query String or even malicious links.
As a result, for deleting purposes we need an HTTPPost action method and the delete button should submit a form that contains the ID of the respective data.
Hence, as you can see in the Index view we have a Form that calls the Cost/Delete action method and pass the ID of the respective Cost Record through asp-route-id. Then inside the Delete Action Method, we can delete the record with the CostRepository.
1 2 3 4 5 6 7 8 9 10 |
[HttpPost] public IActionResult Delete(int id) { costRepository.Delete(id); return RedirectToAction("index"); } |
If you need more details, watch this session video. Also, for being updated about our coming sessions, follow us on Instagram, Facebook, Telegram, or YouTube. Moreover, you can have access to the list of all sessions HERE and you can download this session source code from our GitHub.
You can download this Session Slides form HERE.