Wednesday, September 29, 2021

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

In this article, we will learn CRUD Operations in ASP.NET Core 5.0. We will use Entity Framework Core 5.0 to interact with sql-server database and for performing CRUD operations. 

Before moving ahead, i am going to give you a brief introduction to .NET 5.0 

What is .NET 5.0 ?

The .NET 5.0 is the major release of .NET Core after .Net Core 3.1. We can say that .NET 5 = .NET Core vNext. In .NET 5 lot of new .NET APIs, runtime capabilities and language features has been added. 

The main reason behind presenting .NET 5 is to produce a single .NET runtime and framework that can be used everywhere and that has uniform runtime behaviors.

But the question is why .NET 5.0, why not .NET Core 4.0 ?      

There are two main reasons:

  1. Microsoft skipped version numbers 4.x to avoid confusion with .NET Framework 4.x. 
  2. Microsoft dropped the word "Core" from the name to emphasize that this is the main implementation of .NET going forward. 

To Avoid Confusion:

  1. ASP.NET Core 5.0 is based on .NET 5.0 but retains the name "Core" to avoid confusing it with ASP.NET MVC 5.
  2. Entity Framework Core 5.0 retains the name "Core" to avoid confusing it with Entity Framework 5. 

So we can say that,  there is no .NET Core 5.0, now everything falls under one umbrella, which is .NET 5. 

The whole idea is to bring all .NET runtimes into a single .NET platform with unified base class libraries (BCL) for all kinds of applications like ASP.NET Core, Windows Forms, WPF, Blazor etc.

I Will Cover  The Following Points In This Article:

  1. Basic prerequisites to run .Net core 5.0 project.
  2. How to create ASP.NET CORE 5.0 project in visual studio.
  3. ASP.NET CORE 5.0 project structure.
  4. How to install all the necessory packages from Nuget.
  5. Create Database and required tables for CRUD operation.
  6. Create model and context class from an existing database.
  7. Create Employee controller.
  8. Miscellaneous Configuration to run ASP.NET CORE 5.0 project.
  9. Implement ASP.NET Core MVC CRUD Operations.
  10. Validations in ASP.NET Core MVC.
  11. Run the application.
  12. Instructions to download and run the project.
  13. Summary.

Step 1 - Prerequisites

  1. Visual Studio 2019 latest version(at least 16.6.0). 

  2. .NET SDK 5.0  or later.

  3. Sql-Server 2017 or later version.

Note
In case your visual studio version is lower then mentioned above, you can upgrade using visual studio installer.

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

You will have the option to update in case it is not up to date.

Step 2 - Create ASP.NET Core 5.0 Project. 

Open Visual Studio and click on "Create a new project". 

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

Select the ASP.NET Core Web App(Model-View-Controller) as a project template and click Next

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

Enter the Project name and click Next. 

CRUD Operations In ASP.NET Core MVC (.NET 5.0) 

In additional information, select the fields as configured below and click on Create

CRUD Operations In ASP.NET Core MVC (.NET 5.0)          

Step 3 - ASP.NET CORE 5.0 Project Structure

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

  • Dependencies: It contains all the installed NuGet packages. We can manage the NuGet packages by right clicking on it.
  • Properties: It contains launchSettings.json file which has visual studio profiles, iis and debug setting.
  • wwwroot folder: It is the web root folder of asp.net core application where we can put all the static files such as  javascript , css , images.
  • Controllers: It contails all the controller class we create in our asp.net core mvc application.
  • Models: We can put all the model or view model classes inside this folder.
  • Views: We can add views for certain actions methods inside view folder. There will be seperate folder for each view we create inside Views folder.
  • appsettings.json: It is the application configuration file which is used to store configuration settings i.e connections strings of the database, global variables etc. 
  • Program.cs : Initially asp.net core application starts as a console application. In the Main method it calls the CreateWebHostBuilder() method that configures the asp.net core setting and launch it as asp.net core application.
  • Startup.cs:  It contains the ConfigureServices() and Configure methods. As the name implies ConfigureServices() method configures all the services which are going to used by the application. Configure method take care of all the request processing pipelines.

Step 4 - Install All Necessary Packages From NuGet

Right-click on Dependencies and then Manage NuGet Package. 

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

In order to access the MS SQL Server database, we need to install the below provider. Search “Microsoft.EntityFrameworkCore.SqlServer” as below and install. 

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

To execute EF Core commands we will require EF-Core tools. Search “Microsoft.EntityFrameworkCore.Tools” as below and install

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

This package will allow us to execute scaffold , migration commands from package manager console (PMC). It will also help you to create database context and the model classes. 

Step 5 - Create Database And Employee Table

Create a new database named CompanyDB in sql-server and execute the below SQL query to create employee table.

CREATE TABLE [dbo].[Employees](
[EmployeeId] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NOT NULL,
[Address] [varchar](250) NULL,
[Designation] [varchar](50) NULL,
[Salary] [decimal](18, 0) NOT NULL,
[JoiningDate] [datetime] NOT NULL,
PRIMARY KEY CLUSTERED
(
[EmployeeId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Employees] ADD  DEFAULT (getdate()) FOR [JoiningDate]
GO
SQL

Step 6 - Create Model And Context Class From An Existing Database.

Creating model and context class from an existing database is also called Database-First approach. So to reverse engineer we need to execute Scaffold-DbContext command. This scaffold command will create models and context classes based on the database schema.

Run the below scaffold command after replacing server name, database name with your applications connection setting. 

Scaffold-DbContext “Server=******;Database=ComapnyDB;Integrated Security=True” Microsoft.EntityFrameworkCore.SqlServer -OutputDir Models
C#

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

Above scaffold, commands have three parameters.

First parameter of the scaffold command have server name, database name and integrated security information.

Server=******;Database=ComapnyDB;Integrated Security=True
C#

Second parameter have information about provider. we are  using sql-server so provider will be Microsoft.EntityFrameworkCore.SqlServer.

Microsoft.EntityFrameworkCore.SqlServer
C#

Third parameter i.e -OutputDir is use to specify the location where we want to generate model classes. In our cases it is Models folder.

-OutputDir Models
C#

Step 7 - Create Employee Controller

On the controller folder, Right-click and then Add > Controller. Select the controller template as highlighted below and Click Add.  In the dialog, provided next type name of the controller, in our case it is EmployeeController.cs and click Add to create EmployeeController class under controller folder.  

 CRUD Operations In ASP.NET Core MVC (.NET 5.0)

Finally the employee controller class has been created with basic auto generated code for crud operation. 

CRUD Operations In ASP.NET Core MVC (.NET 5.0)

Step 8 - Miscellaneous Configuration

Store connection string inside appsettings.json and remove auto generated OnConfiguring() method from dbcontext class as it is not good practice to have connection string inside OnConfiguring() method.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "CompanyDB": "Server=******;Database=CompanyDB;Integrated Security=True;"
  }
}
C#

    In the Startup.cs class add CompanyDBContext as a service inside ConfigureService() method as below. We will retrieve the connection string value from appsettings.json file through IConfiguration object's GetConnectionString() method.

      public Startup(IConfiguration configuration)
      {
        Configuration = configuration;
      }
      public IConfiguration Configuration { get; }
      
      // This method gets called by the runtime. Use this method to add services to the container.
      public void ConfigureServices(IServiceCollection services)
      {
        var connectionString = Configuration.GetConnectionString("CompanyDB");
        services.AddDbContextPool<CompanyDBContext>(option =>
        option.UseSqlServer(connectionString));
        services.AddControllersWithViews();
      }
      C#

      In order load Employee Controller's Index view on application start, we simply need to change controller name to Employee inside Configure() method.

        app.UseEndpoints(endpoints =>
        {
          endpoints.MapControllerRoute(
          name: "default",
          pattern: "{controller=Employee}/{action=Index}/{id?}");
        });
        C#

        Inject CompnyDBContext object in the Employees controller's constructor using dependency injection.   

        public class EmployeeController : Controller
        {
          private readonly CompanyDBContext _context;
        
          public EmployeeController(CompanyDBContext context)
          {
            _context = context;
          }
        ------
        -------
        }
        C#

        Step 9 - ASP.NET Core MVC CRUD Operations

        General Instructions To Add Views,

        Under Views folder, Create a new folder named Employee. Click on the Employee folder and click Add.

        Then Click on View and click on the Razor View – Empty template and then click on Add to create the view. 

        CRUD Operations In ASP.NET Core MVC (.NET 5.0)

        After adding all the views for CRUD operation, the Employee folder will have the following views file.

        CRUD Operations In ASP.NET Core MVC (.NET 5.0)

        Index Action Method

        Replace your Index action method with the below code inside the employee controller.                     

        public async Task<IActionResult> Index()
        {
            var employees = await _context.Employees.ToListAsync();
            return View(employees);
        }
        C#

        Replace auto generated Index View code with below code,

        @model IEnumerable<DotNet5Crud.Models.Employee>
        
        @{
            ViewData["Title"] = "Index";
            Layout = "~/Views/Shared/_Layout.cshtml";
        }
        
        <h1>Employee List</h1>
        
        <p style="text-align:right;margin-right:20px;">
            <a class="btn btn-outline-primary" asp-action="AddOrEdit">Create New</a>
        </p>
        <div class="table-responsive">
            <table class="table">
                <thead>
                    <tr>
                        <th>
                            @Html.DisplayNameFor(model => model.Name)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.Designation)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.Address)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.Salary)
                        </th>
                        <th>
                            @Html.DisplayNameFor(model => model.JoiningDate)
                        </th>
                        <th></th>
                        <th>Edit Action</th>
                        <th>Details Action</th>
                        <th>Delete Action</th>
                    </tr>
                </thead>
                <tbody>
                    @foreach (var item in Model)
                    {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Name)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Designation)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Address)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Salary)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.JoiningDate)
                        </td>
                        <td>
                        <td class="text-center">
                            <a asp-action="AddOrEdit" class="btn btn-outline-primary"  asp-route-employeeId ="@item.EmployeeId">Edit</a>
                        </td>
                        <td class="text-center">
                            <a asp-action="Details" class="btn btn-outline-info"  asp-route-employeeId="@item.EmployeeId">Details</a>
                        </td>
                        <td class="text-center">
                            <a asp-action="Delete" class="btn btn-outline-danger" asp-route-employeeId ="@item.EmployeeId">Delete</a>
                        </td>
                    </tr>
                    }
                </tbody>
            </table>
            </div>
        C#

        Index

        It returns all the employees from employee table and presents it to the index view.

        AddOrEdit Action Method

        Replace Create and Edit action methods with AddOrEdit single action method below,

        //AddOrEdit Get Method
        public async Task<IActionResult> AddOrEdit(int? employeeId)
        {
            ViewBag.PageName = employeeId == null ? "Create Employee" : "Edit Employee";
            ViewBag.IsEdit = employeeId == null ? false : true;
            if (employeeId == null)
            {
                return View();
            }
            else
            {
                var employee = await _context.Employees.FindAsync(employeeId);
        
                if (employee == null)
                {
                    return NotFound();
                }
                return View(employee);
            }
        }
        
        //AddOrEdit Post Method
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> AddOrEdit(int employeeId, [Bind("EmployeeId,Name,Designation,Address,Salary,JoiningDate")]
        Employee employeeData)
        {
            bool IsEmployeeExist = false;
        
            Employee employee = await _context.Employees.FindAsync(employeeId);
        
            if (employee != null)
            {
                IsEmployeeExist = true;
            }
            else
            {
                employee = new Employee();
            }
        
            if (ModelState.IsValid)
            {
                try
                {
                    employee.Name = employeeData.Name;
                    employee.Designation = employeeData.Designation;
                    employee.Address = employeeData.Address;
                    employee.Salary = employeeData.Salary;
                    employee.JoiningDate = employeeData.JoiningDate;
        
                   if(IsEmployeeExist)
                    {
                        _context.Update(employee);
                    }
                    else
                    {
                        _context.Add(employee);
                    }
                    await _context.SaveChangesAsync();
                }
                catch (DbUpdateConcurrencyException)
                {
                    throw;
                }
                return RedirectToAction(nameof(Index));
            }
            return View(employeeData);
        }
        C#

        Replace below code in AddOrEdit view,

        @model DotNet5Crud.Models.Employee
        
        @{
            ViewData["Title"] = "Create";
            Layout = "~/Views/Shared/_Layout.cshtml";
        }
        
        <div class="container p-3 my-3 border" >
            <h1> @ViewBag.PageName</h1>
        
            <div class="row">
                <div class="col-sm-6">
                    <hr />
                    <form asp-action="AddOrEdit">
                        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
                        @if (@ViewBag.IsEdit)
                        {
                            <input type="hidden" asp-for="EmployeeId" />
                        }
                        <div class="form-group">
                            <label asp-for="Name" class="control-label"></label>
                            <input asp-for="Name" class="form-control" />
                            <span asp-validation-for="Name" class="text-danger"></span>
                        </div>
                        <div class="form-group">
                            <label asp-for="Designation" class="control-label"></label>
                            <input asp-for="Designation" class="form-control" />
                            <span asp-validation-for="Designation" class="text-danger"></span>
                        </div>
                        <div class="form-group">
                            <label asp-for="Address" class="control-label"></label>
                            <input asp-for="Address" class="form-control" />
                            <span asp-validation-for="Address" class="text-danger"></span>
                        </div>
                        <div class="form-group">
                            <label asp-for="Salary" class="control-label"></label>
                            <input asp-for="Salary" class="form-control" />
                            <span asp-validation-for="Salary" class="text-danger"></span>
                        </div>
                        <div class="form-group">
                            <label asp-for="JoiningDate" class="control-label"></label>
                            <input asp-for="JoiningDate" class="form-control" />
                            <span asp-validation-for="JoiningDate" class="text-danger"></span>
                        </div>
                        <div class="form-group">
                            <input class="btn btn-primary" type="submit" value="Save" />
                            <a class="btn btn-danger" asp-action="Index">Back</a>
                        </div>
                    </form>
                </div>
            </div>
        </div>
        @section Scripts {
            @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
        }
        C#

        Create

        It takes employee details as input and creates a new employee record in the employee table.

        Edit

        It takes employee details as input and updates the new details in the employee table.

        Details Action Method

        Replace Details action method code with below code snippet. 

        // Employee Details
        public async Task<IActionResult> Details(int? employeeId)
        {
            if (employeeId == null)
            {
                return NotFound();
            }
            var employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == employeeId);
            if (employee == null)
            {
                return NotFound();
            }
            return View(employee);
        }
        C#

        Replace below code in Details view,

        @model DotNet5Crud.Models.Employee
        @{
            ViewData["Title"] = "Details";
            Layout = "~/Views/Shared/_Layout.cshtml";
        }
        
        <div class="container p-3 my-3 border">
            <h1>Employee Details</h1>
            <hr />
            <dl class="row">
                <dt class="col-sm-3">
                    @Html.DisplayNameFor(model => model.Name)
                </dt>
                <dd class="col-sm-9">
                    @Html.DisplayFor(model => model.Name)
                </dd>
                <dt class="col-sm-3">
                    @Html.DisplayNameFor(model => model.Designation)
                </dt>
                <dd class="col-sm-9">
                    @Html.DisplayFor(model => model.Designation)
                </dd>
                <dt class="col-sm-3">
                    @Html.DisplayNameFor(model => model.Address)
                </dt>
                <dd class="col-sm-9">
                    @Html.DisplayFor(model => model.Address)
                </dd>
                <dt class="col-sm-3">
                    @Html.DisplayNameFor(model => model.Salary)
                </dt>
                <dd class="col-sm-9">
                    @Html.DisplayFor(model => model.Salary)
                </dd>
                <dt class="col-sm-3">
                    @Html.DisplayNameFor(model => model.JoiningDate)
                </dt>
                <dd class="col-sm-9">
                    @Html.DisplayFor(model => model.JoiningDate)
                </dd>
            </dl>
            <div>
                <a class="btn btn-primary" asp-action="AddOrEdit" asp-route-employeeId="@Model.EmployeeId">Edit</a>
                <a class="btn btn-danger" asp-action="Index">Back</a>
            </div>
        </div>
        C#

        Details

        It returns the employee details from the employee table by employee ID. 

        Delete Action Method

        Replace Delete action method code with the below code snippet. 

        // GET: Employees/Delete/1
        public async Task<IActionResult> Delete(int? employeeId)
        {
            if (employeeId == null)
            {
                return NotFound();
            }
            var employee = await _context.Employees.FirstOrDefaultAsync(m => m.EmployeeId == employeeId);
        
            return View(employee);
        }
        
        // POST: Employees/Delete/1
        [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Delete(int employeeId)
        {
            var employee = await _context.Employees.FindAsync(employeeId);
            _context.Employees.Remove(employee);
            await _context.SaveChangesAsync();
        
            return RedirectToAction(nameof(Index));
        }
        C#

        Replace below code in Delete view: 

        @model DotNet5Crud.Models.Employee
        
        @{
            ViewData["Title"] = "Delete";
            Layout = "~/Views/Shared/_Layout.cshtml";
        }
        
        <h2>Deleting Employee</h2>
        
        <h3>Would you like to continue?</h3>
        <div class="container p-3 my-3 border">
            <hr />
                <dl class="row">
                    <dt class="col-sm-3">
                        @Html.DisplayNameFor(model => model.Name)
                    </dt>
                    <dd class="col-sm-9">
                        @Html.DisplayFor(model => model.Name)
                    </dd>
                    <dt class="col-sm-3">
                        @Html.DisplayNameFor(model => model.Designation)
                    </dt>
                    <dd class="col-sm-9">
                        @Html.DisplayFor(model => model.Designation)
                    </dd>
                    <dt class="col-sm-3">
                        @Html.DisplayNameFor(model => model.Address)
                    </dt>
                    <dd class="col-sm-9">
                        @Html.DisplayFor(model => model.Address)
                    </dd>
                    <dt class="col-sm-3">
                        @Html.DisplayNameFor(model => model.Salary)
                    </dt>
                    <dd class="col-sm-9">
                        @Html.DisplayFor(model => model.Salary)
                    </dd>
                    <dt class="col-sm-3">
                        @Html.DisplayNameFor(model => model.JoiningDate)
                    </dt>
                    <dd class="col-sm-9">
                        @Html.DisplayFor(model => model.JoiningDate)
                    </dd>
                </dl>
        
                    <form asp-action="Delete">
                        <input type="hidden" asp-for="EmployeeId" />
                        <input class="btn btn-primary" type="submit" value="Yes" />
                        <a class="btn btn-danger" asp-action="Index">No</a>
                    </form>
        
         </div>
        C#

        Delete: it takes the employee ID as input, and after confirm delete popup, deletes the employee from employee table.

        Step 10 - Validations In ASP.NET Core

         We need to check whether the provided input is correct or not before submitting the data. We perform Client side as well as Server side validation for this purpose.

        Model Validation

        To perform model validation, we need to use vaidation attributes presents in the System.ComponentModel.DataAnnotations namespace. These attributes decides the validation rules for the model properties.

        Below are the few importent built-in model attributes,

        • [Required] - its validates that the input field should not be null or empty.
        • [Compare] - it validate that the two model fields match or not.
        • [Range] - it validate that the provided input is in the specified range or not.
        • [StringLength] - it validate that the provided string input should not exceed the specified limit.
        • [ValidateNever] - if any model property is decorated with VlaidateNever attribues, it mean this property is excluded from validation.

        In our crud operation, we have created EmployeeValidator class with properties that need to perform model validation. In the name property we set Required and MaxLenghth attribute to validate against for condition, means name should not be empty and it should not exceed maxlength specified.

        public class EmployeeValidator
         {
             [Required]
             [MaxLength(50)]
             [Display(Name = "Employee Name")]
             public string Name { get; set; }
        
             [Required]
             [Display(Name = "Employee Salary")]
             public decimal Salary { get; set; }
        
             [Required]
             [Display(Name = "Joining Date")]
             public decimal JoiningDate { get; set; }
         }
        
         [ModelMetadataType(typeof(EmployeeValidator))]
         public partial class Employee
         {
         }
        C#

        Server Side Validation

        Server side validation performs using ModelState property of ControllerBase class. Generally ModelState handle errors that comes from model binding and model validation. At the execution od the controller action, ModelState.IsValid checks whether any error present or not. If it is true then it will allow to proceed otherwise return the view. 

        Client Side Validation

        Client side validation will not allow the page to submit until the form is valid. It prevent the unnecessary round trip to server. 

        The below script references support client side validation:

        In Layout.cshtml

        <script src="~/lib/jquery/dist/jquery.min.js"></script>
        C#

        In _ValidationScriptsPartial.cshtml 

        <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
        <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
        C#

        Step 11 - Run The Application

         When we Run the application. By default index view will be loaded.

        CRUD Operations In ASP.NET Core MVC (.NET 5.0)

        Click on Create New button to create new employee.

        CRUD Operations In ASP.NET Core MVC (.NET 5.0)

        Click on Edit button to Edit an employee.

        CRUD Operations In ASP.NET Core MVC (.NET 5.0)

         Click on Details button to see the employee detail.

        CRUD Operations In ASP.NET Core MVC (.NET 5.0)

        Click on Delete button to delete the employee.

        CRUD Operations In ASP.NET Core MVC (.NET 5.0)

        In order to download and run the application, please follow the below steps,

        Step 1

        Download the solution files from the attachement of this article.

        Step 2

        Open the solution file in Visual Studio 2019 or later.

        Step 3

        In order to restore the required NuGet packages, rebuild the solution.

        Step 4

        Change the connection string in the appsettings.json file with your SQL Server connection string.

        Step 5

        Run the application.

        How to register multiple implementations of the same interface in Asp.Net Core?

         Problem: I have services that are derived from the same interface. public interface IService { } public class ServiceA : IService { ...