Tutorial - Employee Management System - Part 3 | Creating the ASP.NET Core REST API

Employee Management System - Part 3: Creating the ASP.NET Core REST API (Interactive Build)

EMS: Creating the ASP.NET Core REST API (EmployeeManagement.Api) - Interactive Build

In this part, we will incrementally build our ASP.NET Core Web API. Each sub-section will introduce a new API endpoint, along with the necessary changes to the repository and a final full code for your reference.

Step-by-Step Tutorial Guide (Part 3 - Interactive Build)

Part 3.0: Initial Setup (Recap from Previous Section)

Before we start building the API endpoints, ensure you have completed the following foundational steps from the previous parts:

EmployeeManagement.Models Project:

  • Created the project.
  • Employee.cs (with DepartmentId instead of Department object).
  • Gender.cs (Enum).
  • Department.cs (Class).
  • Crucially, updated Employee.cs with DataAnnotations:
// EmployeeManagement.Models/Employee.cs
using System;
using System.ComponentModel.DataAnnotations;

namespace EmployeeManagement.Models
{
    public class Employee
    {
        public int EmployeeId { get; set; }

        [Required(ErrorMessage = "First Name is required.")]
        [StringLength(100, MinimumLength = 2, ErrorMessage = "First Name must be between 2 and 100 characters.")]
        public string FirstName { get; set; }

        [Required(ErrorMessage = "Last Name is required.")]
        public string LastName { get; set; }

        [Required(ErrorMessage = "Email is required.")]
        [EmailAddress(ErrorMessage = "Invalid Email Format.")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Date of Birth is required.")]
        public DateTime DateOfBrith { get; set; }

        [Required(ErrorMessage = "Gender is required.")]
        public Gender Gender { get; set; }

        public int DepartmentId { get; set; }
        public string PhotoPath { get; set; }
    }
}

EmployeeManagement.Api Project:

  • Created the project (ASP.NET Core Web API template, .NET 8, OpenAPI enabled).
  • Added project reference to EmployeeManagement.Models.
  • Installed NuGet packages: Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore.Tools.
  • Created Models/AppDbContext.cs with DbSets and OnModelCreating for seeding data:
// EmployeeManagement.Api/Models/AppDbContext.cs
using EmployeeManagement.Models;
using Microsoft.EntityFrameworkCore;
using System;

namespace EmployeeManagement.Api.Models
{
    public class AppDbContext : DbContext
    {
        public AppDbContext(DbContextOptions<AppDbContext> options)
            : base(options)
        {
        }

        public DbSet<Employee> Employees { get; set; }
        public DbSet<Department> Departments { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            // Seed Departments Table
            modelBuilder.Entity<Department>().HasData(new Department { DepartmentId = 1, DepartmentName = "IT" });
            modelBuilder.Entity<Department>().HasData(new Department { DepartmentId = 2, DepartmentName = "HR" });
            modelBuilder.Entity<Department>().HasData(new Department { DepartmentId = 3, DepartmentName = "Payroll" });
            modelBuilder.Entity<Department>().HasData(new Department { DepartmentId = 4, DepartmentName = "Admin" });

            // Seed Employee Table
            modelBuilder.Entity<Employee>().HasData(new Employee
            {
                EmployeeId = 1, FirstName = "Raushan", LastName = "Ranjan",
                Email = "raushan@rrtech.com", DateOfBrith = new DateTime(1980, 10, 5),
                Gender = Gender.Male, DepartmentId = 1, PhotoPath = "images/raushan.jpg"
            });
            modelBuilder.Entity<Employee>().HasData(new Employee
            {
                EmployeeId = 2, FirstName = "Sam", LastName = "Galloway",
                Email = "Sam@rrtech.com", DateOfBrith = new DateTime(1981, 12, 22),
                Gender = Gender.Male, DepartmentId = 2, PhotoPath = "images/john.png"
            });
            modelBuilder.Entity<Employee>().HasData(new Employee
            {
                EmployeeId = 3, FirstName = "Pratibha", LastName = "Poddar",
                Email = "pratibha@rrtech.com", DateOfBrith = new DateTime(1979, 11, 11),
                Gender = Gender.Female, DepartmentId = 1, PhotoPath = "images/pratibha.png"
            });
            modelBuilder.Entity<Employee>().HasData(new Employee
            {
                EmployeeId = 4, FirstName = "Sara", LastName = "Longway",
                Email = "sara@rrtech.com", DateOfBrith = new DateTime(1982, 9, 23),
                Gender = Gender.Female, DepartmentId = 3, PhotoPath = "images/mary.png"
            });
        }
    }
}
  • Configured appsettings.json with DBConnection string.
  • Configured Program.cs to add DbContext to DI:
// EmployeeManagement.Api/Program.cs (relevant section)
using EmployeeManagement.Api.Models; // For AppDbContext
using Microsoft.EntityFrameworkCore;   // For UseSqlServer extension method

var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DBConnection")));

// ... rest of Program.cs
  • Ran Add-Migration InitialCreate and Update-Database in Package Manager Console (make sure EmployeeManagement.Api is selected as the default project).

This foundational setup ensures our API project can connect to the database and has the necessary model definitions.

Part 3.1: Defining and Implementing the Repository for Basic Read Operations (GET)

We'll start by defining the interface and implementing the repository methods for retrieving all employees and a single employee by ID.

Step 3.1.1: Define IEmployeeRepository Interface

  • Create Models/IEmployeeRepository.cs: In the EmployeeManagement.Api project, right-click on the Models folder.
  • Select "Add" -> "New Item..." -> "Interface".
  • Name it IEmployeeRepository.cs.
  • Replace its content with the following:
// EmployeeManagement.Api/Models/IEmployeeRepository.cs
using EmployeeManagement.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace EmployeeManagement.Api.Models
{
    public interface IEmployeeRepository
    {
        // Method to get all employees
        Task<IEnumerable<Employee>> GetEmployees();
        // Method to get a single employee by ID
        Task<Employee> GetEmployee(int employeeId);
    }
}

Step 3.1.2: Implement EmployeeRepository for Basic Reads

  • Create Models/EmployeeRepository.cs: In the EmployeeManagement.Api project, right-click on the Models folder.
  • Select "Add" -> "Class...".
  • Name it EmployeeRepository.cs.
  • Replace its content with the following:
// EmployeeManagement.Api/Models/EmployeeRepository.cs
using EmployeeManagement.Models;
using Microsoft.EntityFrameworkCore; // Required for ToListAsync, FirstOrDefaultAsync
using System.Collections.Generic;
using System.Threading.Tasks;

namespace EmployeeManagement.Api.Models
{
    public class EmployeeRepository : IEmployeeRepository
    {
        private readonly AppDbContext appDbContext;

        // Constructor to inject AppDbContext
        public EmployeeRepository(AppDbContext appDbContext)
        {
            this.appDbContext = appDbContext;
        }

        // Implementation to get a single employee by ID
        public async Task<Employee> GetEmployee(int employeeId)
        {
            return await appDbContext.Employees
                .Include(e => e.Department) // Eager load Department
                .FirstOrDefaultAsync(e => e.EmployeeId == employeeId);
        }

        // Implementation to get all employees
        public async Task<IEnumerable<Employee>> GetEmployees()
        {
            return await appDbContext.Employees
                .Include(e => e.Department) // Eager load Department
                .ToListAsync();
        }
    }
}

Step 3.1.3: Register Repository in Program.cs

  • Modify EmployeeManagement.Api/Program.cs: Open Program.cs.
  • Add necessary using statement:
// Add this at the top of Program.cs
using EmployeeManagement.Api.Models; // For IEmployeeRepository and EmployeeRepository
  • Register the repository in the DI container. Add this line within the service configuration section (e.g., after AddDbContext):
// EmployeeManagement.Api/Program.cs (relevant section)
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DBConnection")));

// Register the Employee Repository
builder.Services.AddScoped<IEmployeeRepository, EmployeeRepository>();

var app = builder.Build();
// ... rest of Program.cs

Explanation: AddScoped means a new instance of EmployeeRepository will be created once per client request.

Step 3.1.4: Create EmployeesController for Basic Reads

  • Create Controllers/EmployeesController.cs: In the EmployeeManagement.Api project, right-click on the Controllers folder.
  • Select "Add" -> "Controller..." -> "API Controller - Empty". Click "Add".
  • Name it EmployeesController.cs.
  • Replace its content with the following:
// EmployeeManagement.Api/Controllers/EmployeesController.cs
using EmployeeManagement.Api.Models; // For IEmployeeRepository
using EmployeeManagement.Models;      // For Employee
using Microsoft.AspNetCore.Http;      // For StatusCodes
using Microsoft.AspNetCore.Mvc;       // For ControllerBase, HttpGet etc.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace EmployeeManagement.Api.Controllers
{
    [Route("api/[controller]")] // Base route: /api/employees
    [ApiController]             // Indicates an API controller
    public class EmployeesController : ControllerBase
    {
        private readonly IEmployeeRepository employeeRepository;

        // Constructor injection
        public EmployeesController(IEmployeeRepository employeeRepository)
        {
            this.employeeRepository = employeeRepository;
        }

        // GET: api/employees
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Employee>>> GetEmployees()
        {
            try
            {
                var employees = await employeeRepository.GetEmployees();
                return Ok(employees); // Return 200 OK with data
            }
            catch (Exception)
            {
                // Log the exception in a real application
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error retrieving data from the database"); // Return 500
            }
        }

        // GET: api/employees/{id} (e.g., api/employees/1)
        [HttpGet("{id:int}")] // Route constraint: id must be an integer
        public async Task<ActionResult<Employee>> GetEmployee(int id)
        {
            try
            {
                var result = await employeeRepository.GetEmployee(id);

                if (result == null)
                {
                    return NotFound($"Employee with Id = {id} not found"); // Return 404
                }

                return Ok(result); // Return 200 OK with data
            }
            catch (Exception)
            {
                // Log the exception in a real application
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error retrieving data from the database"); // Return 500
            }
        }
    }
}

Step 3.1.5: Build and Test (GET Operations)

  • Build the EmployeeManagement.Api project (Ctrl+Shift+B).
  • Set EmployeeManagement.Api as the startup project (if not already).
  • Run the project (F5). This will typically open Swagger UI.
  • Test the Endpoints:
    • Expand the Employees controller.
    • Try GET /api/Employees (the one without parameters). Click "Try it out" and "Execute". You should see your seeded employees.
    • Try GET /api/Employees/{id}. Enter 1 in the id field. Click "Try it out" and "Execute". You should see Employee with ID 1.
    • Try GET /api/Employees/{id} with an invalid ID like 99. It should return a 404 Not Found.

Part 3.2: Implementing Create Operation (POST)

Now, let's add the functionality to create new employees via an HTTP POST request.

Step 3.2.1: Update IEmployeeRepository with AddEmployee

  • Modify Models/IEmployeeRepository.cs: Add the AddEmployee method signature:
// EmployeeManagement.Api/Models/IEmployeeRepository.cs
using EmployeeManagement.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace EmployeeManagement.Api.Models
{
    public interface IEmployeeRepository
    {
        Task<IEnumerable<Employee>> GetEmployees();
        Task<Employee> GetEmployee(int employeeId);
        Task<Employee> AddEmployee(Employee employee); // ADD THIS LINE
    }
}

Step 3.2.2: Implement AddEmployee in EmployeeRepository

  • Modify Models/EmployeeRepository.cs: Add the implementation for AddEmployee.
// EmployeeManagement.Api/Models/EmployeeRepository.cs (partial view, focusing on new method)
// ... (existing code)
public class EmployeeRepository : IEmployeeRepository
{
    private readonly AppDbContext appDbContext;

    public EmployeeRepository(AppDbContext appDbContext)
    {
        this.appDbContext = appDbContext;
    }

    // ... (existing GetEmployee, GetEmployees methods)

    public async Task<Employee> AddEmployee(Employee employee)
    {
        var result = await appDbContext.Employees.AddAsync(employee);
        await appDbContext.SaveChangesAsync(); // Save changes to the database
        return result.Entity; // Return the added entity
    }
}

Step 3.2.3: Implement CreateEmployee in EmployeesController (POST)

  • Modify Controllers/EmployeesController.cs: Add using System.Linq; at the top (we'll need Linq later for GetEmployeeByEmail, but for consistency, add it now).
  • Add the HttpPost method:
// EmployeeManagement.Api/Controllers/EmployeesController.cs (partial view, focusing on new method)
// ... (existing using statements)
using System.Linq; // Add this line if not already present

namespace EmployeeManagement.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly IEmployeeRepository employeeRepository;

        public EmployeesController(IEmployeeRepository employeeRepository)
        {
            this.employeeRepository = employeeRepository;
        }

        // ... (existing GetEmployees and GetEmployee methods)

        // POST: api/employees
        [HttpPost]
        public async Task<ActionResult<Employee>> CreateEmployee(Employee employee)
        {
            try
            {
                if (employee == null)
                {
                    return BadRequest(); // Return 400 if request body is empty
                }

                // We will add custom validation here later (e.g., check for duplicate email)
                // For now, it will rely on [ApiController] and DataAnnotations.

                var createdEmployee = await employeeRepository.AddEmployee(employee);

                // Return 201 Created, with the URI of the new resource and its content
                return CreatedAtAction(nameof(GetEmployee), // Points to the GetEmployee action
                                       new { id = createdEmployee.EmployeeId }, // Route values for GetEmployee
                                       createdEmployee); // The created object
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error creating new employee record");
            }
        }
    }
}

Step 3.2.4: Build and Test (POST Operation)

  • Build the EmployeeManagement.Api project.
  • Run the project (F5) to open Swagger UI.
  • Test the POST Endpoint:
    • Expand the Employees controller.
    • Find the POST /api/Employees endpoint. Click "Try it out".
    • In the "Request body" field, enter a JSON representation of a new employee (excluding EmployeeId as it's auto-generated, and ensure DepartmentId points to an existing department, e.g., 1 for IT).
    • {
        "firstName": "Alice",
        "lastName": "Smith",
        "email": "alice.smith@example.com",
        "dateOfBrith": "1990-05-15T00:00:00",
        "gender": 1, // 0 for Male, 1 for Female, 2 for Other
        "departmentId": 2, // Example: HR department
        "photoPath": "images/default.png"
      }
      
    • Click "Execute".
    • Expected Result: A 201 Created status code, and the response body will contain the newly created employee object, including its generated EmployeeId. The Location header in the response will point to the URI of the new employee.
    • You can then use GET /api/Employees again to verify that the new employee is in the list.

Part 3.3: Implementing Update Operation (PUT)

Next, we'll add the functionality to update existing employee records using an HTTP PUT request.

Step 3.3.1: Update IEmployeeRepository with UpdateEmployee

  • Modify Models/IEmployeeRepository.cs: Add the UpdateEmployee method signature:
// EmployeeManagement.Api/Models/IEmployeeRepository.cs
// ... (existing code)
namespace EmployeeManagement.Api.Models
{
    public interface IEmployeeRepository
    {
        Task<IEnumerable<Employee>> GetEmployees();
        Task<Employee> GetEmployee(int employeeId);
        Task<Employee> AddEmployee(Employee employee);
        Task<Employee> UpdateEmployee(Employee employee); // ADD THIS LINE
    }
}

Step 3.3.2: Implement UpdateEmployee in EmployeeRepository

  • Modify Models/EmployeeRepository.cs: Add the implementation for UpdateEmployee.
// EmployeeManagement.Api/Models/EmployeeRepository.cs (partial view, focusing on new method)
// ... (existing code)
public class EmployeeRepository : IEmployeeRepository
{
    private readonly AppDbContext appDbContext;

    public EmployeeRepository(AppDbContext appDbContext)
    {
        this.appDbContext = appDbContext;
    }

    // ... (existing methods)

    public async Task<Employee> UpdateEmployee(Employee employee)
    {
        // Find the existing employee in the database
        var result = await appDbContext.Employees
            .FirstOrDefaultAsync(e => e.EmployeeId == employee.EmployeeId);

        if (result != null)
        {
            // Update properties of the existing employee with values from the incoming employee object
            result.FirstName = employee.FirstName;
            result.LastName = employee.LastName;
            result.Email = employee.Email;
            result.DateOfBrith = employee.DateOfBrith;
            result.Gender = employee.Gender;
            result.DepartmentId = employee.DepartmentId;
            result.PhotoPath = employee.PhotoPath;

            await appDbContext.SaveChangesAsync(); // Save changes
            return result; // Return the updated entity
        }
        return null; // Employee to update not found
    }
}

Step 3.3.3: Implement UpdateEmployee in EmployeesController (PUT)

  • Modify Controllers/EmployeesController.cs: Add the HttpPut method:
// EmployeeManagement.Api/Controllers/EmployeesController.cs (partial view, focusing on new method)
// ... (existing code)
namespace EmployeeManagement.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly IEmployeeRepository employeeRepository;

        public EmployeesController(IEmployeeRepository employeeRepository)
        {
            this.employeeRepository = employeeRepository;
        }

        // ... (existing GET and POST methods)

        // PUT: api/employees/{id} (e.g., api/employees/1)
        [HttpPut("{id:int}")] // Route parameter 'id' for the employee to update
        public async Task<ActionResult<Employee>> UpdateEmployee(int id, Employee employee)
        {
            try
            {
                // Ensure the ID in the URL matches the ID in the request body
                if (id != employee.EmployeeId)
                {
                    return BadRequest("Employee ID mismatch");
                }

                // Get the existing employee from the database
                var employeeToUpdate = await employeeRepository.GetEmployee(id);

                // If the employee to update is not found, return 404
                if (employeeToUpdate == null)
                {
                    return NotFound($"Employee with Id = {id} not found");
                }

                // Update the employee using the repository and return the updated employee
                return Ok(await employeeRepository.UpdateEmployee(employee)); // Return 200 OK with the updated employee
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error updating data");
            }
        }
    }
}
  • Note: We return Ok() with the updated employee. A 200 OK status with the updated resource in the body is a common response for a successful PUT.

Step 3.3.4: Build and Test (PUT Operation)

  • Build the EmployeeManagement.Api project.
  • Run the project (F5) to open Swagger UI.
  • Test the PUT Endpoint:
    • First, use GET /api/Employees/{id} to retrieve an existing employee (e.g., ID 1). Copy its JSON data.
    • Find the PUT /api/Employees/{id} endpoint. Click "Try it out".
    • In the id field, enter the ID of the employee you want to update (e.g., 1).
    • In the "Request body" field, paste the copied JSON. Make some changes (e.g., change FirstName, LastName, or Email). Ensure EmployeeId in the body matches the id in the URL.
    • {
        "employeeId": 1, // MUST match the ID in the URL
        "firstName": "Raushan Updated",
        "lastName": "Ranjan",
        "email": "raushan.updated@rrtech.com",
        "dateOfBrith": "1980-10-05T00:00:00",
        "gender": 0,
        "departmentId": 1,
        "photoPath": "images/raushan.jpg"
      }
      
    • Click "Execute".
    • Expected Result: A 200 OK status code, and the response body will contain the updated employee object.
    • Use GET /api/Employees/{id} again to verify the changes were saved to the database.

Part 3.4: Implementing Delete Operation (DELETE)

Now, let's add the functionality to delete employee records using an HTTP DELETE request.

Step 3.4.1: Update IEmployeeRepository with DeleteEmployee

  • Modify Models/IEmployeeRepository.cs: Add the DeleteEmployee method signature:
// EmployeeManagement.Api/Models/IEmployeeRepository.cs
// ... (existing code)
namespace EmployeeManagement.Api.Models
{
    public interface IEmployeeRepository
    {
        Task<IEnumerable<Employee>> GetEmployees();
        Task<Employee> GetEmployee(int employeeId);
        Task<Employee> AddEmployee(Employee employee);
        Task<Employee> UpdateEmployee(Employee employee);
        Task<Employee> DeleteEmployee(int employeeId); // ADD THIS LINE
    }
}

Step 3.4.2: Implement DeleteEmployee in EmployeeRepository

  • Modify Models/EmployeeRepository.cs: Add the implementation for DeleteEmployee.
// EmployeeManagement.Api/Models/EmployeeRepository.cs (partial view, focusing on new method)
// ... (existing code)
public class EmployeeRepository : IEmployeeRepository
{
    private readonly AppDbContext appDbContext;

    public EmployeeRepository(AppDbContext appDbContext)
    {
        this.appDbContext = appDbContext;
    }

    // ... (existing methods)

    public async Task<Employee> DeleteEmployee(int employeeId)
    {
        // Find the employee to delete
        var result = await appDbContext.Employees
            .FirstOrDefaultAsync(e => e.EmployeeId == employeeId);
        if (result != null)
        {
            appDbContext.Employees.Remove(result); // Remove the entity
            await appDbContext.SaveChangesAsync(); // Save changes
            return result; // Return the deleted entity
        }
        return null; // Employee not found
    }
}

Step 3.4.3: Implement DeleteEmployee in EmployeesController (DELETE)

  • Modify Controllers/EmployeesController.cs: Add the HttpDelete method:
// EmployeeManagement.Api/Controllers/EmployeesController.cs (partial view, focusing on new method)
// ... (existing code)
namespace EmployeeManagement.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly IEmployeeRepository employeeRepository;

        public EmployeesController(IEmployeeRepository employeeRepository)
        {
            this.employeeRepository = employeeRepository;
        }

        // ... (existing GET, POST, PUT methods)

        // DELETE: api/employees/{id} (e.g., api/employees/1)
        [HttpDelete("{id:int}")] // Route parameter 'id' for the employee to delete
        public async Task<ActionResult<Employee>> DeleteEmployee(int id)
        {
            try
            {
                // Get the employee to delete from the database
                var employeeToDelete = await employeeRepository.GetEmployee(id);

                // If the employee is not found, return 404
                if (employeeToDelete == null)
                {
                    return NotFound($"Employee with Id = {id} not found");
                }

                // Delete the employee using the repository
                await employeeRepository.DeleteEmployee(id);
                // Return 200 OK with the deleted employee (or 204 No Content if no body is desired)
                return Ok(employeeToDelete);
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error deleting data");
            }
        }
    }
}
  • Note: Returning Ok(employeeToDelete) sends back the deleted resource. NoContent() (status 204) is also a valid response for DELETE if you don't need to send the resource back.

Step 3.4.4: Build and Test (DELETE Operation)

  • Build the EmployeeManagement.Api project.
  • Run the project (F5) to open Swagger UI.
  • Test the DELETE Endpoint:
    • Identify an EmployeeId you wish to delete (e.g., the Alice Smith you created earlier).
    • Find the DELETE /api/Employees/{id} endpoint. Click "Try it out".
    • In the id field, enter the ID of the employee to delete.
    • Click "Execute".
    • Expected Result: A 200 OK status code (or 204 No Content if you changed the return type).
    • Use GET /api/Employees again to confirm that the employee is no longer in the list.

Part 3.5: Implementing Search Operation (GET with Query Parameters)

Finally, let's add the search functionality, which will allow filtering employees by name and/or gender using query parameters. We'll also add a custom validation for the POST endpoint to prevent duplicate emails.

Step 3.5.1: Update IEmployeeRepository with Search and GetEmployeeByEmail

  • Modify Models/IEmployeeRepository.cs: Add the Search and GetEmployeeByEmail method signatures:
// EmployeeManagement.Api/Models/IEmployeeRepository.cs
// ... (existing code)
namespace EmployeeManagement.Api.Models
{
    public interface IEmployeeRepository
    {
        Task<IEnumerable<Employee>> GetEmployees();
        Task<Employee> GetEmployee(int employeeId);
        Task<Employee> AddEmployee(Employee employee);
        Task<Employee> UpdateEmployee(Employee employee);
        Task<Employee> DeleteEmployee(int employeeId);
        Task<IEnumerable<Employee>> Search(string name, Gender? gender); // ADD THIS LINE
        Task<Employee> GetEmployeeByEmail(string email); // ADD THIS LINE (for custom validation)
    }
}

Step 3.5.2: Implement Search and GetEmployeeByEmail in EmployeeRepository

  • Modify Models/EmployeeRepository.cs: Add the implementations for Search and GetEmployeeByEmail.
// EmployeeManagement.Api/Models/EmployeeRepository.cs (partial view, focusing on new methods)
// ... (existing code)
using System.Linq; // Ensure this is present for .Where() and .Any()

namespace EmployeeManagement.Api.Models
{
    public class EmployeeRepository : IEmployeeRepository
    {
        private readonly AppDbContext appDbContext;

        public EmployeeRepository(AppDbContext appDbContext)
        {
            this.appDbContext = appDbContext;
        }

        // ... (existing methods: GetEmployee, GetEmployees, AddEmployee, UpdateEmployee, DeleteEmployee)

        public async Task<Employee> GetEmployeeByEmail(string email)
        {
            return await appDbContext.Employees
                .FirstOrDefaultAsync(e => e.Email == email);
        }

        public async Task<IEnumerable<Employee>> Search(string name, Gender? gender)
        {
            // Start with all employees
            IQueryable<Employee> query = appDbContext.Employees;

            // Apply name filter if provided
            if (!string.IsNullOrEmpty(name))
            {
                query = query.Where(e => e.FirstName.Contains(name) ||
                                           e.LastName.Contains(name));
            }

            // Apply gender filter if provided
            if (gender != null)
            {
                query = query.Where(e => e.Gender == gender);
            }

            // Execute the query and return results
            return await query.ToListAsync();
        }
    }
}

Step 3.5.3: Update EmployeesController for Search and Custom Validation

  • Modify Controllers/EmployeesController.cs: Update the CreateEmployee (POST) method to include the custom email validation:
// EmployeeManagement.Api/Controllers/EmployeesController.cs (Update the existing POST method)
// ... (existing code)
[HttpPost]
public async Task<ActionResult<Employee>> CreateEmployee(Employee employee)
{
    try
    {
        if (employee == null)
        {
            return BadRequest();
        }

        // Custom validation: Check if an employee with the same email already exists
        var existingEmployee = await employeeRepository.GetEmployeeByEmail(employee.Email);
        if (existingEmployee != null)
        {
            // Add a custom error to the ModelState
            ModelState.AddModelError("email", "Employee email already in use");
            // Return 400 Bad Request with validation errors
            return BadRequest(ModelState);
        }

        var createdEmployee = await employeeRepository.AddEmployee(employee);

        return CreatedAtAction(nameof(GetEmployee),
                               new { id = createdEmployee.EmployeeId },
                               createdEmployee);
    }
    catch (Exception)
    {
        return StatusCode(StatusCodes.Status500InternalServerError,
            "Error creating new employee record");
    }
}
// ... (rest of the controller)
  • Add the Search (GET) method:
// EmployeeManagement.Api/Controllers/EmployeesController.cs (Add this method)
// ... (existing code after DELETE method)

// GET: api/employees/search?name=John&gender=Male
[HttpGet("search")] // Specifies a distinct route segment for search, parameters come from query string
public async Task<ActionResult<IEnumerable<Employee>>> Search(string name, Gender? gender)
{
    try
    {
        var result = await employeeRepository.Search(name, gender);

        if (result.Any()) // Check if any results were returned
        {
            return Ok(result); // Return 200 OK with search results
        }

        return NotFound("No employees found matching the search criteria."); // Return 404
    }
    catch (Exception)
    {
        return StatusCode(StatusCodes.Status500InternalServerError,
            "Error retrieving data from the database for search.");
    }
}

Step 3.5.4: Build and Test (Search and Custom Validation)

  • Build the EmployeeManagement.Api project.
  • Run the project (F5) to open Swagger UI.
  • Test the Search Endpoint:
    • Find the GET /api/Employees/search endpoint. Click "Try it out".
    • Enter values for name (e.g., "Raushan") and/or gender (0 for Male, 1 for Female, 2 for Other).
    • Click "Execute".
    • Expected Result: A 200 OK with matching employees, or 404 Not Found if no results.
  • Test the Custom Email Validation (POST):
    • Find the POST /api/Employees endpoint. Click "Try it out".
    • Enter JSON for a new employee, but use an Email that already exists in your seeded data (e.g., "raushan@rrtech.com").
    • {
        "firstName": "New",
        "lastName": "User",
        "email": "raushan@rrtech.com", // Duplicate email!
        "dateOfBrith": "2000-01-01T00:00:00",
        "gender": 0,
        "departmentId": 1,
        "photoPath": "images/default.png"
      }
      
    • Click "Execute".
    • Expected Result: A 400 Bad Request status code with a response body containing validation errors, including your custom "Employee email already in use" message.

Full Code Reference (End of Part 3)

Here are the complete code files for the IEmployeeRepository interface, its EmployeeRepository implementation, and the EmployeesController after completing all operations (GET, POST, PUT, DELETE, Search, and custom validation).

Models/IEmployeeRepository.cs (Complete)

using EmployeeManagement.Models;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace EmployeeManagement.Api.Models
{
    public interface IEmployeeRepository
    {
        Task<IEnumerable<Employee>> GetEmployees();
        Task<Employee> GetEmployee(int employeeId);
        Task<Employee> AddEmployee(Employee employee);
        Task<Employee> UpdateEmployee(Employee employee);
        Task<Employee> DeleteEmployee(int employeeId);
        Task<IEnumerable<Employee>> Search(string name, Gender? gender);
        Task<Employee> GetEmployeeByEmail(string email);
    }
}

Models/EmployeeRepository.cs (Complete)

using EmployeeManagement.Models;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq; // Added for .Any() and LINQ queries
using System.Threading.Tasks;

namespace EmployeeManagement.Api.Models
{
    public class EmployeeRepository : IEmployeeRepository
    {
        private readonly AppDbContext appDbContext;

        public EmployeeRepository(AppDbContext appDbContext)
        {
            this.appDbContext = appDbContext;
        }

        public async Task<Employee> AddEmployee(Employee employee)
        {
            var result = await appDbContext.Employees.AddAsync(employee);
            await appDbContext.SaveChangesAsync();
            return result.Entity;
        }

        public async Task<Employee> DeleteEmployee(int employeeId)
        {
            var result = await appDbContext.Employees
                .FirstOrDefaultAsync(e => e.EmployeeId == employeeId);
            if (result != null)
            {
                appDbContext.Employees.Remove(result);
                await appDbContext.SaveChangesAsync();
                return result;
            }
            return null;
        }

        public async Task<Employee> GetEmployee(int employeeId)
        {
            return await appDbContext.Employees
                .Include(e => e.Department)
                .FirstOrDefaultAsync(e => e.EmployeeId == employeeId);
        }

        public async Task<Employee> GetEmployeeByEmail(string email)
        {
            return await appDbContext.Employees
                .FirstOrDefaultAsync(e => e.Email == email);
        }

        public async Task<IEnumerable<Employee>> GetEmployees()
        {
            return await appDbContext.Employees
                .Include(e => e.Department)
                .ToListAsync();
        }

        public async Task<IEnumerable<Employee>> Search(string name, Gender? gender)
        {
            IQueryable<Employee> query = appDbContext.Employees;

            if (!string.IsNullOrEmpty(name))
            {
                query = query.Where(e => e.FirstName.Contains(name) ||
                                           e.LastName.Contains(name));
            }

            if (gender != null)
            {
                query = query.Where(e => e.Gender == gender);
            }

            return await query.ToListAsync();
        }

        public async Task<Employee> UpdateEmployee(Employee employee)
        {
            var result = await appDbContext.Employees
                .FirstOrDefaultAsync(e => e.EmployeeId == employee.EmployeeId);

            if (result != null)
            {
                result.FirstName = employee.FirstName;
                result.LastName = employee.LastName;
                result.Email = employee.Email;
                result.DateOfBrith = employee.DateOfBrith;
                result.Gender = employee.Gender;
                result.DepartmentId = employee.DepartmentId;
                result.PhotoPath = employee.PhotoPath;

                await appDbContext.SaveChangesAsync();
                return result;
            }
            return null;
        }
    }
}

Controllers/EmployeesController.cs (Complete)

using EmployeeManagement.Api.Models;
using EmployeeManagement.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace EmployeeManagement.Api.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployeesController : ControllerBase
    {
        private readonly IEmployeeRepository employeeRepository;

        public EmployeesController(IEmployeeRepository employeeRepository)
        {
            this.employeeRepository = employeeRepository;
        }

        // GET: api/employees
        [HttpGet]
        public async Task<ActionResult<IEnumerable<Employee>>> GetEmployees()
        {
            try
            {
                var employees = await employeeRepository.GetEmployees();
                return Ok(employees);
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error retrieving data from the database.");
            }
        }

        // GET: api/employees/{id}
        [HttpGet("{id:int}")]
        public async Task<ActionResult<Employee>> GetEmployee(int id)
        {
            try
            {
                var result = await employeeRepository.GetEmployee(id);

                if (result == null)
                {
                    return NotFound($"Employee with Id = {id} not found.");
                }

                return Ok(result);
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error retrieving data from the database.");
            }
        }

        // POST: api/employees
        [HttpPost]
        public async Task<ActionResult<Employee>> CreateEmployee(Employee employee)
        {
            try
            {
                if (employee == null)
                {
                    return BadRequest();
                }

                // Custom validation: Check if an employee with the same email already exists
                var existingEmployee = await employeeRepository.GetEmployeeByEmail(employee.Email);
                if (existingEmployee != null)
                {
                    ModelState.AddModelError("email", "Employee email already in use.");
                    return BadRequest(ModelState);
                }

                var createdEmployee = await employeeRepository.AddEmployee(employee);

                return CreatedAtAction(nameof(GetEmployee),
                                       new { id = createdEmployee.EmployeeId },
                                       createdEmployee);
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error creating new employee record.");
            }
        }

        // PUT: api/employees/{id}
        [HttpPut("{id:int}")]
        public async Task<ActionResult<Employee>> UpdateEmployee(int id, Employee employee)
        {
            try
            {
                if (id != employee.EmployeeId)
                {
                    return BadRequest("Employee ID mismatch.");
                }

                var employeeToUpdate = await employeeRepository.GetEmployee(id);

                if (employeeToUpdate == null)
                {
                    return NotFound($"Employee with Id = {id} not found.");
                }

                return Ok(await employeeRepository.UpdateEmployee(employee));
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error updating data.");
            }
        }

        // DELETE: api/employees/{id}
        [HttpDelete("{id:int}")]
        public async Task<ActionResult<Employee>> DeleteEmployee(int id)
        {
            try
            {
                var employeeToDelete = await employeeRepository.GetEmployee(id);

                if (employeeToDelete == null)
                {
                    return NotFound($"Employee with Id = {id} not found.");
                }

                await employeeRepository.DeleteEmployee(id);
                return Ok(employeeToDelete);
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error deleting data.");
            }
        }

        // GET: api/employees/search?name=John&gender=Male
        [HttpGet("search")]
        public async Task<ActionResult<IEnumerable<Employee>>> Search(string name, Gender? gender)
        {
            try
            {
                var result = await employeeRepository.Search(name, gender);

                if (result.Any())
                {
                    return Ok(result);
                }

                return NotFound("No employees found matching the search criteria.");
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error retrieving data from the database for search.");
            }
        }
    }
}

Comments

Popular posts from this blog

Blazor: Building Web Apps with C# - Introduction

Blazor WebAssembly Hosted App Tutorial: Envelope Tracker System

Securing MVC-based Applications Using Blazor