Tutorial - Employee Management System - Part 3 | Creating the ASP.NET Core REST API
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(withDepartmentIdinstead ofDepartmentobject).Gender.cs(Enum).Department.cs(Class).- Crucially, updated
Employee.cswith 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.cswithDbSetsandOnModelCreatingfor 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.jsonwithDBConnectionstring. - Configured
Program.csto addDbContextto 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 InitialCreateandUpdate-Databasein Package Manager Console (make sureEmployeeManagement.Apiis 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 theEmployeeManagement.Apiproject, right-click on theModelsfolder. - 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 theEmployeeManagement.Apiproject, right-click on theModelsfolder. - 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: OpenProgram.cs. - Add necessary
usingstatement:
// 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 theEmployeeManagement.Apiproject, right-click on theControllersfolder. - 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.Apiproject (Ctrl+Shift+B). - Set
EmployeeManagement.Apias 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 theAddEmployeemethod 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 forAddEmployee.
// 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: Addusing System.Linq;at the top (we'll need Linq later forGetEmployeeByEmail, but for consistency, add it now). - Add the
HttpPostmethod:
// 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.Apiproject. - Run the project (
F5) to open Swagger UI. - Test the POST Endpoint:
- Expand the Employees controller.
- Find the
POST /api/Employeesendpoint. Click "Try it out". - In the "Request body" field, enter a JSON representation of a new employee (excluding
EmployeeIdas it's auto-generated, and ensureDepartmentIdpoints to an existing department, e.g., 1 for IT). - Click "Execute".
- Expected Result: A
201 Createdstatus code, and the response body will contain the newly created employee object, including its generatedEmployeeId. TheLocationheader in the response will point to the URI of the new employee. - You can then use
GET /api/Employeesagain to verify that the new employee is in the list.
{ "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" }
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 theUpdateEmployeemethod 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 forUpdateEmployee.
// 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 theHttpPutmethod:
// 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.Apiproject. - 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
idfield, 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, orEmail). EnsureEmployeeIdin the body matches theidin the URL. - Click "Execute".
- Expected Result: A
200 OKstatus 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.
{ "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" } - First, use
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 theDeleteEmployeemethod 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 forDeleteEmployee.
// 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 theHttpDeletemethod:
// 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 forDELETEif you don't need to send the resource back.
Step 3.4.4: Build and Test (DELETE Operation)
- Build the
EmployeeManagement.Apiproject. - Run the project (
F5) to open Swagger UI. - Test the DELETE Endpoint:
- Identify an
EmployeeIdyou wish to delete (e.g., the Alice Smith you created earlier). - Find the
DELETE /api/Employees/{id}endpoint. Click "Try it out". - In the
idfield, enter the ID of the employee to delete. - Click "Execute".
- Expected Result: A
200 OKstatus code (or204 No Contentif you changed the return type). - Use
GET /api/Employeesagain to confirm that the employee is no longer in the list.
- Identify an
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 theSearchandGetEmployeeByEmailmethod 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 forSearchandGetEmployeeByEmail.
// 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 theCreateEmployee(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.Apiproject. - Run the project (
F5) to open Swagger UI. - Test the Search Endpoint:
- Find the
GET /api/Employees/searchendpoint. Click "Try it out". - Enter values for
name(e.g., "Raushan") and/orgender(0 for Male, 1 for Female, 2 for Other). - Click "Execute".
- Expected Result: A
200 OKwith matching employees, or404 Not Foundif no results.
- Find the
- Test the Custom Email Validation (POST):
- Find the
POST /api/Employeesendpoint. Click "Try it out". - Enter JSON for a new employee, but use an
Emailthat already exists in your seeded data (e.g., "raushan@rrtech.com"). - Click "Execute".
- Expected Result: A
400 Bad Requeststatus code with a response body containing validation errors, including your custom "Employee email already in use" message.
{ "firstName": "New", "lastName": "User", "email": "raushan@rrtech.com", // Duplicate email! "dateOfBrith": "2000-01-01T00:00:00", "gender": 0, "departmentId": 1, "photoPath": "images/default.png" } - Find the
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
Post a Comment