Category Module in ECommerce Application

How to Design and Develop Category Module in ECommerce Application

In this article, I will discuss How to Design and Develop the Category Module in our ECommerce Application using ASP.NET Core Web API and EF Core. Please read our previous article discussing the Address Module in our ECommerce Application.

The Category Module plays an important role in organizing the products within the E-Commerce platform, enhancing navigation, filtering, and overall management. By categorizing products into logical groups, this module improves user experience, facilitates inventory management, and supports targeted marketing strategies. The module offers comprehensive CRUD (Create, Read, Update, Delete) operations, enabling administrators to manage product categories and efficiently maintain an organized product catalog.

Key Features of the Category Module

The following are the Key Features of the Category Module that we will implement in our Ecommerce Application:

CRUD Operations
  • Create: Add new product categories to organize merchandise logically.
  • Read: Retrieve details of existing categories for display and management purposes.
  • Update: Modify existing category information to reflect changes in product organization or business requirements.
  • Delete: Remove categories that are no longer needed, ensuring that categories with associated products are handled appropriately to maintain data integrity.
Association with Products
  • Linking Products to Categories: Each category can have multiple products, facilitating organized product listings and easier customer navigation.
Data Validation and Integrity
  • Unique Category Names: Ensures each category name is unique to prevent confusion and maintain a clear product hierarchy.
  • Input Validation: Applies data annotations to enforce rules on category properties, such as name length and description limits, maintaining consistent and high-quality data.
Data Transfer Objects (DTOs)

DTOs are essential for defining the structure of data exchanged between the client and server, ensuring that only relevant and validated data is processed. They help in maintaining a clear separation between the internal data models and the exposed API contracts, enhancing security and flexibility. Now, let us create a folder named CategoryDTOs within the DTOs folder where we all add the Required Request and Response DTOs for managing the Categories.

CategoryCreateDTO

Create a class file named CategoryCreateDTO.cs within the DTOs/CategoryDTOs folder, and then copy and paste the following code. The CategoryCreateDTO Facilitates the creation of product categories by encapsulating category-related data. It is used when administrators add a new category, ensuring the input data adheres to validation rules before processing.

using System.ComponentModel.DataAnnotations;
namespace ECommerceApp.DTOs.CategoryDTOs
{
    // DTO for creating a new category
    public class CategoryCreateDTO
    {
        [Required(ErrorMessage = "Category Name is required.")]
        [StringLength(100, MinimumLength = 3, ErrorMessage = "Category Name must be between 3 and 100 characters.")]
        public string Name { get; set; }

        [StringLength(500, ErrorMessage = "Description cannot exceed 500 characters.")]
        public string Description { get; set; }
    }
}
CategoryResponseDTO

Create a class file named CategoryResponseDTO.cs within the DTOs/CategoryDTOs folder, then copy and paste the following code. The CategoryResponseDTO Represents the response structure for category-related data, providing a clean and consistent format for client consumption. It is used in API responses to deliver category details to clients, ensuring that only necessary information is exposed and maintaining a standardized response format.

namespace ECommerceApp.DTOs.CategoryDTOs
{
    // DTO for returning category details.
    public class CategoryResponseDTO
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public bool IsActive { get; set; }
    }
}
CategoryUpdateDTO

Create a class file named CategoryUpdateDTO.cs within the DTOs/CategoryDTOs folder, then copy and paste the following code. The CategoryUpdateDTO Facilitates the updation of existing product categories by encapsulating category-related data. It is used when administrators update a category, ensuring the input data adheres to validation rules before processing.

using System.ComponentModel.DataAnnotations;
namespace ECommerceApp.DTOs.CategoryDTOs
{
    // DTO for updating an existing category
    public class CategoryUpdateDTO
    {
        [Required(ErrorMessage = "Category Id is required.")]
        public int Id { get; set; }

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

        [StringLength(500, ErrorMessage = "Description cannot exceed 500 characters.")]
        public string Description { get; set; }
    }
}
Category Service Class

The Category service class encapsulates all business logic and data access operations required for category management, ensuring that the controller remains clean and focused solely on handling HTTP requests and responses. So, create a class file named CategoryService.cs within the Services folder and then copy and paste the following code:

using Microsoft.EntityFrameworkCore;
using ECommerceApp.Data;
using ECommerceApp.DTOs.CategoryDTOs;
using ECommerceApp.Models;
using ECommerceApp.DTOs;

namespace ECommerceApp.Services
{
    public class CategoryService
    {
        private readonly ApplicationDbContext _context;

        public CategoryService(ApplicationDbContext context)
        {
            _context = context;
        }

        public async Task<ApiResponse<CategoryResponseDTO>> CreateCategoryAsync(CategoryCreateDTO categoryDto)
        {
            try
            {
                // Check if category name already exists (case-insensitive)
                if (await _context.Categories.AnyAsync(c => c.Name.ToLower() == categoryDto.Name.ToLower()))
                {
                    return new ApiResponse<CategoryResponseDTO>(400, "Category name already exists.");
                }

                // Manual mapping from DTO to Model
                var category = new Category
                {
                    Name = categoryDto.Name,
                    Description = categoryDto.Description,
                    IsActive = true
                };

                // Add category to the database
                _context.Categories.Add(category);
                await _context.SaveChangesAsync();

                // Map to CategoryResponseDTO
                var categoryResponse = new CategoryResponseDTO
                {
                    Id = category.Id,
                    Name = category.Name,
                    Description = category.Description,
                    IsActive = category.IsActive
                };

                return new ApiResponse<CategoryResponseDTO>(200, categoryResponse);
            }
            catch (Exception ex)
            {
                // Log the exception (implementation depends on your logging setup)
                return new ApiResponse<CategoryResponseDTO>(500, $"An unexpected error occurred while processing your request, Error: {ex.Message}");
            }
        }

        public async Task<ApiResponse<CategoryResponseDTO>> GetCategoryByIdAsync(int id)
        {
            try
            {
                var category = await _context.Categories
                    .AsNoTracking()
                    .FirstOrDefaultAsync(c => c.Id == id);

                if (category == null)
                {
                    return new ApiResponse<CategoryResponseDTO>(404, "Category not found.");
                }

                // Map to CategoryResponseDTO
                var categoryResponse = new CategoryResponseDTO
                {
                    Id = category.Id,
                    Name = category.Name,
                    Description = category.Description,
                    IsActive = category.IsActive
                };

                return new ApiResponse<CategoryResponseDTO>(200, categoryResponse);
            }
            catch (Exception ex)
            {
                // Log the exception
                return new ApiResponse<CategoryResponseDTO>(500, $"An unexpected error occurred while processing your request, Error: {ex.Message}");
            }
        }

        public async Task<ApiResponse<ConfirmationResponseDTO>> UpdateCategoryAsync(CategoryUpdateDTO categoryDto)
        {
            try
            {
                var category = await _context.Categories.FindAsync(categoryDto.Id);
                if (category == null)
                {
                    return new ApiResponse<ConfirmationResponseDTO>(404, "Category not found.");
                }

                // Check if the new category name already exists (excluding current category)
                if (await _context.Categories.AnyAsync(c => c.Name.ToLower() == categoryDto.Name.ToLower() && c.Id != categoryDto.Id))
                {
                    return new ApiResponse<ConfirmationResponseDTO>(400, "Another category with the same name already exists.");
                }

                // Update category properties manually
                category.Name = categoryDto.Name;
                category.Description = categoryDto.Description;

                await _context.SaveChangesAsync();

                // Prepare confirmation message
                var confirmationMessage = new ConfirmationResponseDTO
                {
                    Message = $"Category with Id {categoryDto.Id} updated successfully."
                };

                return new ApiResponse<ConfirmationResponseDTO>(200, confirmationMessage);
            }
            catch (Exception ex)
            {
                // Log the exception
                return new ApiResponse<ConfirmationResponseDTO>(500, $"An unexpected error occurred while processing your request, Error: {ex.Message}");
            }
        }

        public async Task<ApiResponse<ConfirmationResponseDTO>> DeleteCategoryAsync(int id)
        {
            try
            {
                var category = await _context.Categories
                    .Include(c => c.Products)
                    .FirstOrDefaultAsync(c => c.Id == id);

                if (category == null)
                {
                    return new ApiResponse<ConfirmationResponseDTO>(404, "Category not found.");
                }

                //Soft Delete
                category.IsActive = false;
                await _context.SaveChangesAsync();

                // Prepare confirmation message
                var confirmationMessage = new ConfirmationResponseDTO
                {
                    Message = $"Category with Id {id} deleted successfully."
                };

                return new ApiResponse<ConfirmationResponseDTO>(200, confirmationMessage);
            }
            catch (Exception ex)
            {
                // Log the exception
                return new ApiResponse<ConfirmationResponseDTO>(500, $"An unexpected error occurred while processing your request, Error: {ex.Message}");
            }
        }

        public async Task<ApiResponse<List<CategoryResponseDTO>>> GetAllCategoriesAsync()
        {
            try
            {
                var categories = await _context.Categories
                    .AsNoTracking()
                    .ToListAsync();

                var categoryList = categories.Select(c => new CategoryResponseDTO
                {
                    Id = c.Id,
                    Name = c.Name,
                    Description = c.Description,
                    IsActive = c.IsActive
                }).ToList();

                return new ApiResponse<List<CategoryResponseDTO>>(200, categoryList);
            }
            catch (Exception ex)
            {
                // Log the exception
                return new ApiResponse<List<CategoryResponseDTO>>(500, $"An unexpected error occurred while processing your request, Error: {ex.Message}");
            }
        }
    }
}
Registering Services in Dependency Injection Container

To enable dependency injection of the service class, you need to register it within your application’s dependency injection container. This is typically done in the Program.cs file. So, please modify the Program class as follows:

using ECommerceApp.Data;
using ECommerceApp.Services;
using Microsoft.EntityFrameworkCore;

namespace ECommerceApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.

            builder.Services.AddControllers()
            .AddJsonOptions(options =>
            {
                // This will use the property names as defined in the C# model
                options.JsonSerializerOptions.PropertyNamingPolicy = null;
            });

            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            // Configure EF Core with SQL Server
            builder.Services.AddDbContext<ApplicationDbContext>(options =>
                options.UseSqlServer(builder.Configuration.GetConnectionString("EFCoreDBConnection")));

            // Registering the CustomerService
            builder.Services.AddScoped<CustomerService>();

            // Registering the AddressService
            builder.Services.AddScoped<AddressService>();

            // Registering the CategoryService
            builder.Services.AddScoped<CategoryService>();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            app.MapControllers();

            app.Run();
        }
    }
}
Categories Controller

The CategoriesController now utilizes the CategoryService to handle category-related operations. This refactoring results in a cleaner controller that delegates business logic to the service, promoting better code organization and maintainability. The Categories Controller manages all product category operations, ensuring administrators can efficiently create, retrieve, update, and delete categories.

Create a new API Empty Controller named CategoriesController within the Controllers folder and then copy and paste the following code. Each action method is designed to handle specific CRUD operations, enforcing validation and maintaining data integrity.

using Microsoft.AspNetCore.Mvc;
using ECommerceApp.DTOs;
using ECommerceApp.DTOs.CategoryDTOs;
using ECommerceApp.Services;

namespace ECommerceApp.Controllers
{
    [ApiController]
    [Route("api/[controller]")]
    public class CategoriesController : ControllerBase
    {
        private readonly CategoryService _categoryService;

        // Injecting the CategoryService
        public CategoriesController(CategoryService categoryService)
        {
            _categoryService = categoryService;
        }

        // Creates a new category.
        [HttpPost("CreateCategory")]
        public async Task<ActionResult<ApiResponse<CategoryResponseDTO>>> CreateCategory([FromBody] CategoryCreateDTO categoryDto)
        {
            var response = await _categoryService.CreateCategoryAsync(categoryDto);
            if (response.StatusCode != 200)
            {
                return StatusCode(response.StatusCode, response);
            }
            return Ok(response);
        }

        // Retrieves a category by ID.
        [HttpGet("GetCategoryById/{id}")]
        public async Task<ActionResult<ApiResponse<CategoryResponseDTO>>> GetCategoryById(int id)
        {
            var response = await _categoryService.GetCategoryByIdAsync(id);
            if (response.StatusCode != 200)
            {
                return StatusCode(response.StatusCode, response);
            }
            return Ok(response);
        }

        // Updates an existing category.
        [HttpPut("UpdateCategory")]
        public async Task<ActionResult<ApiResponse<ConfirmationResponseDTO>>> UpdateCategory([FromBody] CategoryUpdateDTO categoryDto)
        {
            var response = await _categoryService.UpdateCategoryAsync(categoryDto);
            if (response.StatusCode != 200)
            {
                return StatusCode(response.StatusCode, response);
            }
            return Ok(response);
        }

        // Deletes a category by ID.
        [HttpDelete("DeleteCategory/{id}")]
        public async Task<ActionResult<ApiResponse<ConfirmationResponseDTO>>> DeleteCategory(int id)
        {
            var response = await _categoryService.DeleteCategoryAsync(id);
            if (response.StatusCode != 200)
            {
                return StatusCode(response.StatusCode, response);
            }
            return Ok(response);
        }

        // Retrieves all categories.
        [HttpGet("GetAllCategories")]
        public async Task<ActionResult<ApiResponse<List<CategoryResponseDTO>>>> GetAllCategories()
        {
            var response = await _categoryService.GetAllCategoriesAsync();
            if (response.StatusCode != 200)
            {
                return StatusCode(response.StatusCode, response);
            }
            return Ok(response);
        }
    }
}
Testing the Category Module Endpoints of Our ECommerce Application:
Create Category:

Allows administrators to add new product categories to the system. It is invoked when an administrator submits a request to create a new category, providing necessary details like name and description. This endpoint Allows administrators to add new product categories to the system.

Endpoint: POST /api/Categories/CreateCategory
Method: POST
URL: http://localhost:5000/api/Categories/CreateCategory
Headers: Content-Type: application/json
Request Body:

{
    "Name": "Clothing",
    "Description": "A wide range of Shirts, Pants, Dresses, and Outerwear"
}

Response in Swagger:

How to Design and Develop the Category Module in our ECommerce Application using ASP.NET Core Web API and EF Core

Get Category by ID:

Retrieves detailed information about a specific category based on its unique identifier. It is used when administrators or clients need to view details of a particular category, such as its name and description. This endpoint retrieves detailed information about a specific category.

Endpoint: GET /api/Categories/GetCategoryById/{id}
Method: GET
URL: http://localhost:5000/api/Categories/GetCategoryById/3
Response in Swagger:

How to Design and Develop the Category Module in our ECommerce Application using ASP.NET Core Web AP

Update Category:

Enables administrators to modify existing category information. It is invoked when an administrator updates a category’s name or description to reflect product organization or business requirements changes. This endpoint enables administrators to modify existing category information.

Endpoint: PUT /api/Categories/UpdateCategory
Method: PUT
URL: http://localhost:5000/api/Categories/UpdateCategory
Headers: Content-Type: application/json
Request Body:

{
    "Id": 3,
    "Name": "Mens Clothing",
    "Description": "A wide range of Shirts, Pants, and Outerwears for Men"
}

Response in Swagger:

Category Module in our ECommerce Application using ASP.NET Core Web AP

Delete Category:

Allows administrators to remove a category from the system. It is used when a category is no longer needed or should be retired, provided it doesn’t have associated products. This endpoint allows administrators to remove a category from the system.

Endpoint: DELETE /api/Categories/DeleteCategory/{id}
URL: http://localhost:5000/api/Categories/DeleteCategory/3
Method: DELETE
Response in Swagger:

Category Module in our ECommerce Application

Get All Categories:

Retrieves a comprehensive list of all product categories in the system. It is used when administrators or clients need to view all available categories, such as populating dropdowns or displaying category listings. This endpoint retrieves a comprehensive list of all product categories in the system.

Endpoint: GET /api/Categories/GetAllCategories
Method: GET
URL: http://localhost:5000/api/Categories/GetAllCategories
Response in Swagger:

Category Module

So, we have completed the Category Module Implementation in our Ecommerce Application using ASP.NET Core Web API and Entity Framework Core. In the next article, I will discuss How to Implement the Product Module of our ECommerce Application for Product Inventory Management. In this article, we discussed How to Design and Develop the Category Module for our ECommerce Application using ASP.NET Core Web API and EF Core, and I hope you enjoy the article.

Leave a Reply

Your email address will not be published. Required fields are marked *