CRUD Operations with ASP.NET Core API and EF Core

Introduction

In my previous article, I explained step by step how to create and map a model to a database table in .NET 8 using Entity Framework Core. The process involves defining a model, creating a DbContext, configuring the database connection, and using migrations to keep your database schema in sync with your models.

Create a Model with a Database Table in .NET 8 using EF Core.

What is a Model-View-Controller (MVC)?

This article will explore the MVC architectural pattern widely used in software application development. MVC stands for Model-View-Controller, one of the most effective design patterns for creating web, desktop, and mobile applications. It is particularly prevalent in frameworks like ASP.NET Core MVC, Ruby on Rails, and Laravel PHP.

  • Model (M): Manages the application's data and business logic.
  • View (V): Displays the data and handles the user interface.
  • Controller (C): Processes user input and coordinates between Model and View.

Controller

Picture 1. I’ll explain how the MVC architecture works step by step, followed by a diagram.

  1. The client first sends a request to the server.
  2. The controller receives the request and forwards it to the model.
  3. The model interacts with the database and executes any necessary business logic.
  4. After retrieving the data from the database, the model returns it to the controller.
  5. The controller passes the data to the view.
  6. The view formats the data.
  7. The formatted data is sent back to the client.

This explanation covers the basic steps of the MVC architecture, along with an accompanying image. I’m not providing a comprehensive overview of MVC here, as I’ll be focusing on the MVC pattern specifically for an ASP.NET application.

Step 1. Setting Up the Project.

First, create a new .NET 8 project. In this example, we'll make an ASP.NET Core Web API project, but the process is similar for other types of .NET applications.

  1. Open Visual Studio and click on Create a new project. Select ASP.NET Core Web API, and click on the Next button.
  2. Create a project as a Student_Registration and the Solution name as a Student_Registration. Click on the Next button.
  3. Select the Framework as a long-term support .net 8.0 and Click on the Create Button.
  4. After clicking on the create button, the Student_Registration project was created.

Create button

Picture 2. See the Solution Explorer Student_Registration, my project is a Student_Registration.

Step 2. Define the Model Class.

Now I'm going to explain what the class is about.

What is a Class with Attributes?

A class is a blueprint or template for creating objects in programming. It defines the structure and behavior of the objects by including attributes (also known as properties or fields) and methods (functions that define actions).

Attributes

  • Attributes are variables defined inside a class.
  • They represent the data or characteristics of an object.
  • Each object created from a class will have its values for these attributes.

Example Explanation

Let’s take the example of a Student class.

  1. Class Name: Student (defines the blueprint).
  2. Attributes: Name, Address, Age, Gender, and Phone_Number (describe the characteristics of a student).

Attributes

Picture 3. Class Name: Student. And Attributes: Name, Address, Age, Gender, and Phone_Num.

Create Models

Now, I'm going to create the Models folder in my project. Right-click on your project to open a pop-up menu, then click 'Add,' select 'New Folder,' and name it 'Models.'

Create Models

Picture 4. Create 'Models’ Folder.

Inside the Models folder, create a new class file. Right-click on the Models folder, select 'Add,' then choose 'Class,' click it, and give it the name “Student”.

namespace Student_Registration.Module
{
    public class Student
    {
        public int id { get; set; }
        public string? name { get; set; }
        public int age { get; set; }
        public string? address { get; set; }
        public string? gender { get; set; }
        public string? phoneNum { get; set; }
    }
}

Student

Picture 5. Student Module created.

Step 3. Installing Required NuGet Packages.

You must install specific NuGet packages to use Entity Framework Core in your project. Here's a quick guide on how to do it using various methods:

Using .NET CLI (Command Line Interface)

The .NET CLI provides a simple and efficient way to install NuGet packages through the terminal or command prompt.

Commands to install EF Core packages.

Core Library

dotnet add package Microsoft.EntityFrameworkCore

Tools for Migrations and Scaffolding

dotnet add package Microsoft.EntityFrameworkCore.Tools

Design-Time Features

dotnet add package Microsoft.EntityFrameworkCore.Design

SQL Server Provider: (Required if your project works with SQL Server)

dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Using Package Manager Console (Visual Studio)

If you're using Visual Studio, you can install the required packages through the Package Manager Console.

Commands to install EF Core packages.

Core Library

Install-Package Microsoft.EntityFrameworkCore

Tools for Migrations and Scaffolding

Install-Package Microsoft.EntityFrameworkCore.Tools

Design-Time Features

Install-Package Microsoft.EntityFrameworkCore.Design

SQL Server Provider: (For SQL Server projects)

Install-Package Microsoft.EntityFrameworkCore.SqlServer

Step 4. Configure the Connection String.

Step 4.1. Add the Connection String in appsettings.json.

Open the appsettings.json file in your project.

Add a section for connection strings, like this,

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=YOUR_SERVER_NAME; Database=YOUR_DATABASE_NAME; Integrated Security=True; Encrypt=True; TrustServerCertificate=True;"
  }
}

Explanation of Key Parts

  • Server: Specifies the database server (e.g., localhost or a named SQL Server instance).
  • Database: The name of the database.
  • Integrated Security: If True, use Windows Authentication.
  • Encrypt: Enables encrypted communication with the database.
  • TrustServerCertificate: Skips certificate validation (use cautiously, typically in development).

Appsettings

Picture 6. Add ConnectionStrings.

The following connection string is for SQL Server. To connect successfully, replace the placeholders with your actual database credentials.

Step 4.2. Create the DbContext Class.

In Entity Framework Core, the DbContext class handles database connections and is responsible for querying and saving data. We will create an AppDbContext class to represent our database session and expose the Student model as a DbSet.

To get started, create a folder named Data in the root of your project and add a new class file called AppDbContext.cs.

using Microsoft.EntityFrameworkCore;

using Student_Registration.Module;

namespace Student_Registration.Data
{
    public class AppDbContext: DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options) :base(options) 
        { 
        }  
    Public DbSet<Student> Students { get; set; } 
    }
}

AppDbContext

Picture 7. AppDbContext.

The public class AppDbContext: DbContext defines a custom database context class in Entity Framework Core, inheriting functionality for managing database operations like querying and saving data.

In the above code.

  • DbSet<Student> represents the Students table in the database.
  • The AppDbContext constructor accepts DbContextOptions to configure the database connection.

Step 4.3. Register the DbContext in the Program.cs.

To ensure that Entity Framework Core can access your DbContext, register it in the Program.cs file. Modify the Program.cs to include AppDbContext.

#region Database Configure
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString));
#endregion

Entity Framework Core

Picture 8. This ensures that AppDbContext is available via dependency injection throughout the application.

Step 5. Build your project solution.

Before running migrations in Visual Studio, ensuring your solution builds successfully is important. This step verifies that your code is error-free and ready to proceed with database schema updates. Follow these steps to build your Visual Studio project.

Steps to Build in Visual Studio

  1. Open Your Solution: Launch Visual Studio and open the solution or project you are working on.
  2. Select Build Option: In the menu bar, click on Build.
  3. Choose Build Solution: From the drop-down menu, select Build Solution (or press Ctrl + Shift + B as a shortcut).
  4. Check the Output Window
    • Look at the Output window for building messages.
    • If there are errors, resolve them before proceeding.
    • Warnings can be reviewed but do not prevent the build unless they’re critical.
  5. Verify Success: Ensure the build completes without errors. You’ll see a message like Build succeeded in the Output window.

 Build succeeded

Picture 9. Build your solution.

Why is Building Necessary Before Migrations?

  • The build ensures that all changes in your models are correctly compiled.
  • It prevents runtime errors during migration execution.
  • It confirms that the dependencies and project structure are intact.

Project structure

Picture 10. The solution builds successfully.

Step 6. Create and Apply Migrations.

Migrations help you create or update the database schema to align with your application models.

To access the Package Manager Console in Visual Studio.

  1. Navigate to the Tools menu at the top of Visual Studio.
  2. Select NuGet Package Manager.
  3. Click on Package Manager Console.

Package Manager Console

Picture 11. The solution builds successfully.

To make an initial migration and apply it, run the following commands.

Add-Migration InitialCreate
Update-Database

Initial migration

Picture 12. Migrations Success.

  • Add-Migration InitialCreate: Creates a new migration based on the Student model and AppDbContext.
  • Database-Update: Applies the migration to your database, creating the necessary tables.

Fix Error (globalization-invariant mode)

When Migrations Dou gets this kind of error, the globalization-invariant mode.

System.Globalization.CultureNotFoundException: Only the invariant culture is supported in globalization-invariant mode. See https://aka.ms/GlobalizationInvariantMode for more information. (Parameter 'name') en-us is an invalid culture identifier. at System.Globalization.CultureInfo.GetCultureInfo(String name) at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource1 retry, SqlConnectionOverrides overrides)

Fix Error

Picture 13. globalization-invariant mode.

The error indicates that your application runs in globalization-invariant mode, which restricts certain culture settings, including en-US. This happens when the environment variable DOTNET_SYSTEM_GLOBALIZATION_INVARIANT is set to true or when the .NET runtime is built without ICU (International Components for Unicode) support.

Ensure that DOTNET_SYSTEM_GLOBALIZATION_INVARIANT is not set to true.

Double-click on your project and change the InvariantGlobalization variable to false.

<InvariantGlobalization>false</InvariantGlobalization>

InvariantGlobalization

Picture 14. globalization-invariant mode false.

Let’s check whether the database is created or not.

Database

Picture 15. StudentRegistrationDB.[dbo].[Students] Database created.

Step 7. API testing.

What is Postman?

Postman is a popular API testing and development tool that simplifies the process of interacting with APIs. It provides a graphical user interface (GUI) to send HTTP requests (e.g., GET, POST, PUT, DELETE) and view the responses without needing to write code.

User-friendly interface for developers of all levels. Eliminates the need to write code for basic API testing. It makes it easy to visualize and debug API behavior.

Why Use Postman?

Test API Endpoints

  • Postman allows developers to test API endpoints manually by sending various types of HTTP requests (e.g., GET, POST, PUT, DELETE).
  • This ensures the API behaves as expected before integrating it into an application.

Debug API Issues

Developers can inspect responses, status codes, headers, and errors to identify and fix problems with an API.

Simplify Development

While building APIs, developers can quickly verify CRUD operations and test how the backend processes data.

Support for Different Data Formats

  • Postman supports multiple request body formats such as JSON, XML, form data, and raw text.
  • It also lets you customize headers and authentication.

Automation and Collaboration

  • Developers can save API requests in collections for reuse.
  • Collections can be shared with team members, making collaboration easier.

Supports Authentication

Postman provides options to test APIs with various authentication methods, such as,

  • Basic Auth
  • Bearer Token
  • OAuth 2.0
  • API Keys

Monitor and Document APIs

  • Postman can be used to monitor API uptime and performance.
  • It can also auto-generate API documentation.

Step 8. Web API CURD Operation.

CURD Operation

Picture 16. Implementing CRUD (Create, Read, Update, Delete) operations in a Web API with ASP.NET can be done by following these steps.

Step 8.1. Create the Student API Controller.

Add a New Controller

  • Right-click the Controllers folder.
  • Select Add > Controller > API Controller -Empty.
  • Create > StudentController.cs> Click on the Add Button.

Add Button

Picture 17. Create the StudentController.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Student_Registration.Data;

namespace Student_Registration.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class StudentController : ControllerBase
    {
        private readonly AppDbContext _appDbContext;

        public StudentController(AppDbContext appDbContext)
        {
            _appDbContext = appDbContext;
        }
    }
}

I'll explain the code

public class StudentController : ControllerBase

ControllerBase: Indicates that this class inherits from ControllerBase.ControllerBase provides basic functionality for Web API controllers, such as returning HTTP responses (Ok(), NotFound()) and handling requests.

private readonly AppDbContext _appDbContext;

Refers to the Entity Framework Core database context class used for querying and saving data to the database. Declares a private, read-only field named _appDbContext to interact with the database.

public StudentController(AppDbContext appDbContext)

The name of the constructor must match the name of the class.

AppDbContext app context: A parameter of type AppDbContext that is passed to the constructor. This is an instance of the database context class provided by Dependency Injection (DI).

Defines the constructor for the StudentController class and accepts an AppDbContext instance through Dependency Injection.

_appDbContext = appDbContext;

Assign the injected AppDbContext instance to the private field _appDbContext for use in other controller methods.

Step 8.2. Create the HttpPost.

  • What It Is HTTP POST: An attribute that specifies this method will handle HTTP POST requests. HTTP POST is typically used to create new resources on the server.
  • Why It's Used: This method ensures that when the client sends a POST request to the route associated with this controller, it is executed.
[HttpPost]
public ActionResult<Student> Create([FromBody] Student student)
{
    _appDbContext.Students.Add(student);
    _appDbContext.SaveChanges();
    return Ok(student);
}

I'll explain the code

public ActionResult<Student>Create([FromBody]Student student)

public ActionResult<Student> specifies that the method returns an ActionResult with a Student object in the response.

Create is the method name, likely used to add a new Student record.

[FromBody]Student student binds the HTTP request body (usually JSON) to a Student object for processing.

_appDbContext.Students.Add(student);
  1. _appDbContext: Refers to the injected database context (initialized in the constructor).
  2. Students: The DbSet<Student> in the database context represents the Students table.
  3. Add(student): Adds the new student object to the Students table in the database context. At this stage, the record is not yet saved to the database.
_appDbContext.SaveChanges();

Saves all changes made in the current database context (_appDbContext) to the actual database. Without calling SaveChanges(), the added student record would remain in memory and not be stored in the database.

return Ok(student);

Returns a 200 OK response to the client, including the newly created student record.

Now, I'm going to create the new student record by a postman.

New student record

Picture 18. API Status: 200 OK (Records successfully added).

Check the database to see if the records have been added.

API Status

Picture 19. Add a new student to the database.

Step 8.3. Create the HttpGet.

  • What is HTTP GET: It is used to retrieve data from a server without making any changes to the server's state.
  • Why It's Used: It tells the framework that the associated method should be invoked when a GET request is made to a specific route or endpoint and typically used for retrieving resources or data.
[HttpGet]
public ActionResult<IEnumerable<Student>> GetAll()
{
    return _appDbContext.Students.ToList();
}

I'll explain the code

public ActionResult <IEnumerable<Student>> GetAll()

The method returns an ActionResult containing an IEnumerable<Student> (a collection of Student objects).

_appDbContext.Students.ToList();

_appDbContext is an instance of a database context class.

Students are assumed to be a DbSet<Student> representing the Student table in the database.

.ToList() converts the queryable collection of students into a list in memory.

Queryable collection

Picture 20. API Status: 200 OK.

Step 8.4. Create the HttpGet ("{id: int}")

[HttpGet("{id:int}")]
public ActionResult<Student> GetById(int id)
{
    var student = _appDbContext.Students.Find(id);
    return Ok(student);
}

I'll explain the code

public ActionResult<Student> GetById(int id)
  1. ActionResult<Student>: The method will return an ActionResult containing a Student object.
  2. (int id): This is the method's parameter list. The method takes an integer, ID, as its single parameter. This ID is typically used to identify which Student to retrieve.
    var student = _appDbContext.Students.Find(id);
    
    ​​​​​​​
  3. Students: Refers to the DbSet<Student> collection in the database context, representing the Student table.
  4. Find(id): A method provided by Entity Framework that searches for an entity with the given primary key (id). If a matching record exists, it retrieves the entity; otherwise, it returns null.
  5. return Ok(student): Returns an HTTP 200 OK response, indicating a successful operation.
 

HTTP 200

Picture 21. API Status: 200 OK URL: /api/Student/3

Step 8.5. Create the HttpPut.

  1. What It Is HTTP PUT: An attribute that specifies this method will handle HTTP PUT requests. HTTP PUT is typically used to update an existing resource on the server.
  2. Why It's Used: This method ensures that when the client sends a PUT request to the route associated with this controller, it is executed.
[HttpPut("{id:int}")]
public ActionResult<Student> Update([FromBody] Student student)
{
    _appDbContext.Students.Update(student);
    _appDbContext.SaveChanges();
    return Ok();
}

I'll explain the code.

   public ActionResult<Student> Update([FromBody] Student student)

ActionResult<Student> Update: Defines a publicly accessible PUT method that returns an HTTP response, potentially including a Student object

[FromBody] Student student
  1. [FromBody]: Specifies that the student parameter’s value should be deserialized from the body of the HTTP request. The JSON payload in the request body to the Student object.
  2. Student student: Student: The model or entity representing the data structure for a student. The parameter that holds the data provided in the PUT request.

Now, I’m going to edit the details of the student with ID 3.

{  
    "id": 3,
    "name": "Murukan",
    "age": 23,
    "address": "Kandy, Srilanka",
    "gender": "Male",
    "phoneNum": "0773333335"
}

PUT request

Picture 22. API Status: 200 Success OK URL: /api/Student/3 Updated.

Check the database to see if the records have been updated.

Updated

Picture 23. Data has been successfully updated.

Step 8.6. Create the HttpDelete.

  1. What is [HttpDelete]: The decorated method handles HTTP DELETE requests. DELETE is used to remove resources from the server. It adheres to the RESTful principles for managing resource lifecycle in web APIs.
  2. Why Use: DELETE is one of the standard HTTP methods defined in REST APIs. It is specifically intended for removing resources. The attribute explicitly associates the method with DELETE requests, improving the readability and maintainability of the code.
[HttpDelete("{id:int}")]
public ActionResult DeleteById(int id)
{
    var student = _appDbContext.Students.Find(id);
    
    if (student == null)
    {
        return NotFound(new { Message = $"Student with ID {id} not found." });
    }

    _appDbContext.Students.Remove(student);
    _appDbContext.SaveChanges();
    return Ok();
}

I'll explain the code.

[HttpDelete("{id: int}")] 

Specifies that the method handles HTTP DELETE requests and requires an integer ID in the route to identify the resource to delete.

public ActionResult DeleteById(int id) {}

Defines a method that deletes a resource identified by the id parameter and returns an HTTP response indicating the result of the operation.

var student = _appDbContext.Students.Find(id); 

Retrieves a student entity with the specified ID from the database using the Entity Framework Find method.

if (student == null)
   {
    return NotFound(new { Message = $"Student with ID {id} not found." });
   }

Check if the student object is null, indicating that no matching record was found in the database.

_appDbContext.Students.Remove(student);Â 

Marks the specified student entity for deletion from the database.

_appDbContext.SaveChanges(); 

Commits all pending changes, including deletions, updates, or additions, to the database.

Commit changes

Picture 24. The database contains 5 records.

Now, I'm going to delete the record with ID 5.

Delete record

Picture 25. The record with ID 5 has been successfully deleted.

Successfully Deleted

Picture 26. The table is updated; there are now four records.

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Student_Registration.Data;
using Student_Registration.Module;

namespace Student_Registration.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class StudentController : ControllerBase
    {
        private readonly AppDbContext _appDbContext;

        public StudentController(AppDbContext appDbContext)
        {
            _appDbContext = appDbContext;
        }

        [HttpPost]
        public ActionResult<Student> Create([FromBody] Student student)
        {
            _appDbContext.Students.Add(student);
            _appDbContext.SaveChanges();
            return Ok(student);
        }

        [HttpGet]
        public ActionResult<IEnumerable<Student>> GetAll()
        {
            return _appDbContext.Students.ToList();
        }

        [HttpGet("{id:int}")]
        public ActionResult<Student> GetById(int id)
        {
            var student = _appDbContext.Students.Find(id);
            return Ok(student);
        }

        [HttpPut("{id:int}")]
        public ActionResult<Student> Update([FromBody] Student student)
        {
            _appDbContext.Students.Update(student);
            _appDbContext.SaveChanges();
            return Ok();
        }

        [HttpDelete("{id:int}")]
        public ActionResult DeleteById(int id)
        {
            var student = _appDbContext.Students.Find(id);
            if (student == null)
            {
                return NotFound(new { Message = $"Student with ID {id} not found." });
            }

            _appDbContext.Students.Remove(student);
            _appDbContext.SaveChanges();
            return Ok(new { Message = $"Student with ID {id} was successfully deleted." });
        }
    }
}

Summary

This article explains how to perform basic CRUD operations (Create, Read, Update, Delete) using ASP.NET Core Web API with Entity Framework Core. It covers setting up the project, creating models, configuring the database connection, and using DbContext to interact with the database. Migrations are applied to keep the database schema in sync with the models, and an API controller is created to implement the CRUD operations with HTTP methods like POST, GET, PUT, and DELETE. Finally, the API is tested using Postman to ensure functionality, providing a straightforward guide to integrating data manipulation in web applications.


Orfium Sri Lanka
Globally based Software Developing & Data Processing Company