Room Amenities Module of Hotel Booking Application

Implementing Room Amenities Module of Hotel Booking Application

In this article, I will discuss implementing the Hotel Booking Application Room Amenities Module in our ASP.NET Core Web API Application. Please read our previous article discussing How to Implement the Amenities Module in our Hotel Booking Application.

How to Implement Room Amenity Module of Hotel Booking Application

Let us implement the Room Amenity Module. Let us create the Stored Procedures, DTOs, a Repository class, and a Room Amenity Controller for adding, updating, and deleting Room Amenities, Fetching the Room Amenity Details by ID, Fetching all Room Amenities by Room Type ID and amenity ID, etc. Also, let us implement the Bulk Insert, Update, and Delete functionalities.

Create Stored Procedures for Room Amenity Management

We will start by creating the necessary stored procedures in SQL Server to Manage the Room Amenity Data in the database. Please execute the following Script on the HotelDB database that we are working with so far.

-- Stored Procedure for Fetching All RoomAmenities by RoomTypeID
CREATE OR ALTER PROCEDURE spFetchRoomAmenitiesByRoomTypeID
    @RoomTypeID INT
AS
BEGIN
    SET NOCOUNT ON;
    SELECT a.AmenityID, a.Name, a.Description, a.IsActive
 FROM RoomAmenities ra
 JOIN Amenities a ON ra.AmenityID = a.AmenityID
 WHERE ra.RoomTypeID = @RoomTypeID;
END;
GO

-- Stored Procedure for Fetching All RoomTypes by AmenityID
CREATE OR ALTER PROCEDURE spFetchRoomTypesByAmenityID
    @AmenityID INT
AS
BEGIN
    SET NOCOUNT ON;
    SELECT rt.RoomTypeID, rt.TypeName, rt.Description, rt.AccessibilityFeatures, rt.IsActive
 FROM RoomAmenities ra
 JOIN RoomTypes rt ON ra.RoomTypeID = rt.RoomTypeID
 WHERE ra.AmenityID = @AmenityID;
END;
GO

-- Insert Procedure for RoomAmenities
CREATE OR ALTER PROCEDURE spAddRoomAmenity
    @RoomTypeID INT,
    @AmenityID INT,
    @Status BIT OUTPUT,
    @Message NVARCHAR(255) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION;
            IF NOT EXISTS (SELECT 1 FROM RoomTypes WHERE RoomTypeID = @RoomTypeID) OR
               NOT EXISTS (SELECT 1 FROM Amenities WHERE AmenityID = @AmenityID)
            BEGIN
                SET @Status = 0; -- Failure
                SET @Message = 'Room type or amenity does not exist.';
                ROLLBACK TRANSACTION;
                RETURN;
            END

            IF EXISTS (SELECT 1 FROM RoomAmenities WHERE RoomTypeID = @RoomTypeID AND AmenityID = @AmenityID)
            BEGIN
                SET @Status = 0; -- Failure
                SET @Message = 'This room amenity link already exists.';
                ROLLBACK TRANSACTION;
                RETURN;
            END

            INSERT INTO RoomAmenities (RoomTypeID, AmenityID)
            VALUES (@RoomTypeID, @AmenityID);

            SET @Status = 1; -- Success
            SET @Message = 'Room amenity added successfully.';
            COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
        SET @Status = 0; -- Failure
        SET @Message = ERROR_MESSAGE();
    END CATCH;
END;
GO

-- Deleting a Single RoomAmenities based on RoomTypeID and AmenityID
CREATE OR ALTER PROCEDURE spDeleteSingleRoomAmenity
    @RoomTypeID INT,
    @AmenityID INT,
    @Status BIT OUTPUT,
    @Message NVARCHAR(255) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION;
            DECLARE @Exists BIT;
            SELECT @Exists = COUNT(*) FROM RoomAmenities WHERE RoomTypeID = @RoomTypeID AND AmenityID = @AmenityID;

            IF @Exists = 0
            BEGIN
                SET @Status = 0; -- Failure
                SET @Message = 'The specified RoomTypeID and AmenityID combination does not exist.';
                ROLLBACK TRANSACTION;
                RETURN;
            END

            -- Delete the specified room amenity
            DELETE FROM RoomAmenities
            WHERE RoomTypeID = @RoomTypeID AND AmenityID = @AmenityID;

            SET @Status = 1; -- Success
            SET @Message = 'Room amenity deleted successfully.';
            COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
        SET @Status = 0; -- Failure
        SET @Message = ERROR_MESSAGE();
    END CATCH;
END;
GO

-- Create a User-Defined Table Type
-- This type will be used to pass multiple Amenity IDs as a single parameter to the stored procedures.
CREATE TYPE AmenityIDTableType AS TABLE (AmenityID INT);
GO

-- Stored Procedure for Bulk Insert into RoomAmenities for a Single RoomTypeID
CREATE OR ALTER PROCEDURE spBulkInsertRoomAmenities
    @RoomTypeID INT,
    @AmenityIDs AmenityIDTableType READONLY,
    @Status BIT OUTPUT,
    @Message NVARCHAR(255) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION;
            -- Check if the RoomTypeID exists
            IF NOT EXISTS (SELECT 1 FROM RoomTypes WHERE RoomTypeID = @RoomTypeID)
            BEGIN
                SET @Status = 0; -- Failure
                SET @Message = 'Room type does not exist.';
                ROLLBACK TRANSACTION;
                RETURN;
            END

            -- Check if all AmenityIDs exist
            IF EXISTS (SELECT 1 FROM @AmenityIDs WHERE AmenityID NOT IN (SELECT AmenityID FROM Amenities))
            BEGIN
                SET @Status = 0; -- Failure
                SET @Message = 'One or more amenities do not exist.';
                ROLLBACK TRANSACTION;
                RETURN;
            END

            -- Insert AmenityIDs that do not already exist for the given RoomTypeID
            INSERT INTO RoomAmenities (RoomTypeID, AmenityID)
            SELECT @RoomTypeID, a.AmenityID 
            FROM @AmenityIDs a
            WHERE NOT EXISTS (
                SELECT 1 
                FROM RoomAmenities ra
                WHERE ra.RoomTypeID = @RoomTypeID AND ra.AmenityID = a.AmenityID
            );

            SET @Status = 1; -- Success
            SET @Message = 'Room amenities added successfully.';
            COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
        SET @Status = 0; -- Failure
        SET @Message = ERROR_MESSAGE();
    END CATCH;
END;
GO

-- Stored Procedure for Bulk Update in RoomAmenities of a single @RoomTypeID
CREATE OR ALTER PROCEDURE spBulkUpdateRoomAmenities
    @RoomTypeID INT,
    @AmenityIDs AmenityIDTableType READONLY,
    @Status BIT OUTPUT,
    @Message NVARCHAR(255) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION;
            IF NOT EXISTS (SELECT 1 FROM RoomTypes WHERE RoomTypeID = @RoomTypeID)
            BEGIN
                SET @Status = 0; -- Failure
                SET @Message = 'Room type does not exist.';
                ROLLBACK TRANSACTION;
                RETURN;
            END

            DECLARE @Count INT;
            SELECT @Count = COUNT(*) FROM Amenities WHERE AmenityID IN (SELECT AmenityID FROM @AmenityIDs);
            IF @Count <> (SELECT COUNT(*) FROM @AmenityIDs)
            BEGIN
                SET @Status = 0; -- Failure
                SET @Message = 'One or more amenities do not exist.';
                ROLLBACK TRANSACTION;
                RETURN;
            END

            DELETE FROM RoomAmenities WHERE RoomTypeID = @RoomTypeID;

            INSERT INTO RoomAmenities (RoomTypeID, AmenityID)
            SELECT @RoomTypeID, AmenityID FROM @AmenityIDs;

            SET @Status = 1; -- Success
            SET @Message = 'Room amenities updated successfully.';
            COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        IF @@TRANCOUNT > 0
            ROLLBACK TRANSACTION;
        SET @Status = 0; -- Failure
        SET @Message = ERROR_MESSAGE();
    END CATCH;
END;
GO

-- Deleting All RoomAmenities of a Single RoomTypeID
CREATE OR ALTER PROCEDURE spDeleteAllRoomAmenitiesByRoomTypeID
    @RoomTypeID INT,
    @Status BIT OUTPUT,
    @Message NVARCHAR(255) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION;
            -- Delete all amenities for the specified room type
            DELETE FROM RoomAmenities WHERE RoomTypeID = @RoomTypeID;

            SET @Status = 1; -- Success
            SET @Message = 'All amenities for the room type have been deleted successfully.';
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        SET @Status = 0; -- Failure
        SET @Message = ERROR_MESSAGE();
    END CATCH;
END;
GO

-- Deleting All RoomAmenities of a Single AmenityID
CREATE OR ALTER PROCEDURE spDeleteAllRoomAmenitiesByAmenityID
    @AmenityID INT,
    @Status BIT OUTPUT,
    @Message NVARCHAR(255) OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    BEGIN TRY
        BEGIN TRANSACTION;
            -- Delete all amenities for the specified Amenity ID
            DELETE FROM RoomAmenities WHERE AmenityID = @AmenityID;

            SET @Status = 1; -- Success
            SET @Message = 'All amenities for the Amenity ID have been deleted successfully.';
        COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION;
        SET @Status = 0; -- Failure
        SET @Message = ERROR_MESSAGE();
    END CATCH;
END;
GO
Explanation of Stored Procedures:

Here are the detailed explanations for each of the stored procedures we created above, which are designed to manage the Room Amenities table effectively.

  • spFetchRoomAmenitiesByRoomTypeID: This stored procedure retrieves all amenities associated with a specific room type. It provides a detailed view of what amenities are available in that type of room.
  • spFetchRoomTypesByAmenityID: This stored procedure retrieves all room types that offer a specific amenity. It’s useful for understanding which types of rooms offer particular features.
  • spAddRoomAmenity: This stored procedure is used to add a link between a room type and an amenity, essentially assigning an amenity to a room type. It includes checks to ensure that both the room type and the amenity exist before the link is created and checks for duplicates to prevent redundant entries.
  • spDeleteSingleRoomAmenity: This stored procedure allows for removing a specific amenity from a room type.
  • spBulkInsertRoomAmenities: This stored procedure facilitates the bulk insertion of amenities for a given room type, which can significantly streamline the process of setting up a new room type or updating an existing one.
  • spBulkUpdateRoomAmenities: This stored procedure is used to update the set of amenities associated with a specific room type.
  • spDeleteAllRoomAmenitiesByRoomTypeID and spDeleteAllRoomAmenitiesByAmenityID: These procedures remove all links between a specific room type or amenity and any associated records. They are essential for managing changes in hotel services or discontinuations.
Creating Room Amenity DTOs:

Next, we need to create the DTOs required for performing operations on the Room Amenity database table. Please create a folder called RoomAmenityDTOs inside the DTOs folder, where we will add all the DTOs related to managing Room Amenity.

RoomAmenityDTO

Create a class file named RoomAmenityDTO.cs within the RoomAmenityDTOs folder, and then copy and paste the following code. This DTO will only include the properties required for creating and removing room amenities.

using System.ComponentModel.DataAnnotations;

namespace HotelBookingAPI.DTOs.RoomAmenityDTOs
{
    public class RoomAmenityDTO
    {
        [Required]
        public int RoomTypeID { get; set; }
        [Required]
        public int AmenityID { get; set;}
    }
}
RoomAmenityResponseDTO

Create a class file named RoomAmenityResponseDTO.cs within the RoomAmenityDTOs folder and copy and paste the following code. We will use this DTO to return the response when we perform any Insert, Update, or Delete Operations.

namespace HotelBookingAPI.DTOs.RoomAmenityDTOs
{
    public class RoomAmenityResponseDTO
    {
        public string Message { get; set; }
        public bool IsSuccess { get; set; }
    }
}
AmenityResponseDTO

Create a class file named AmenityResponseDTO.cs within the RoomAmenityDTOs folder, and then copy and paste the following code. This DTO will only include the required properties, which we are returning for the Amenity Fetch Operations based on the RoomTypeId.

namespace HotelBookingAPI.DTOs.RoomAmenityDTOs
{
    public class AmenityResponseDTO
    {
        public int AmenityID { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
        public bool IsActive { get; set; }  
    }
}
FetchRoomAmenityResponseDTO

Create a class file named FetchRoomAmenityResponseDTO.cs within the RoomAmenityDTOs folder, and then copy and paste the following code. This DTO will only include the required properties we are returning for the Room Fetch Operations based on the AmenityId.

namespace HotelBookingAPI.DTOs.RoomAmenityDTOs
{
    public class FetchRoomAmenityResponseDTO
    {
        public int RoomTypeID { get; set; }
        public string TypeName { get; set; }
        public string Description { get; set; }
        public string AccessibilityFeatures { get; set; }
        public bool IsActive { get; set; }
    }
}
RoomAmenitiesBulkInsertUpdateDTO

Create a class file named RoomAmenitiesBulkInsertUpdateDTO.cs within the RoomAmenityDTOs folder, and then copy and paste the following code. This DTO will only include the properties required for bulk operations.

using System.ComponentModel.DataAnnotations;

namespace HotelBookingAPI.DTOs.RoomAmenityDTOs
{
    public class RoomAmenitiesBulkInsertUpdateDTO
    {
        [Required]
        public int RoomTypeID {  get; set; }
        [Required]
        public List<int> AmenityIDs { get; set; }
    }
}
Creating Room Amenity Repository:

Next, we need to create the Room Amenity Repository class, where we will implement the business and data access logic. This class will consume the Room Amenity Related Stored Procedures and DTOs for the operations that we have created so far. So, create a class file named RoomAmenityRepository.cs within the Repository folder and copy and paste the following code.

using HotelBookingAPI.Connection;
using HotelBookingAPI.DTOs.RoomAmenityDTOs;
using Microsoft.Data.SqlClient;
using System.Data;

namespace HotelBookingAPI.Repository
{
    public class RoomAmenityRepository
    {
        private readonly SqlConnectionFactory _connectionFactory;

        public RoomAmenityRepository(SqlConnectionFactory connectionFactory)
        {
            _connectionFactory = connectionFactory;
        }

        //Fetch All Amenities Based on the RoomTypeId
        public async Task<List<AmenityResponseDTO>> FetchRoomAmenitiesByRoomTypeIdAsync(int roomTypeId)
        {
            var response = new List<AmenityResponseDTO>();

            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spFetchRoomAmenitiesByRoomTypeID", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@RoomTypeID", roomTypeId);
           
            await connection.OpenAsync();
            using var reader = await command.ExecuteReaderAsync();
            while (await reader.ReadAsync())
            {
                response.Add(new AmenityResponseDTO
                {
                    AmenityID = reader.GetInt32(reader.GetOrdinal("AmenityID")),
                    Name = reader.GetString(reader.GetOrdinal("Name")),
                    Description = reader.GetString(reader.GetOrdinal("Description")),
                    IsActive = reader.GetBoolean(reader.GetOrdinal("IsActive"))
                });
            }

            return response;
        }

        //Fetch All Room Types Based on the AmenityID
        public async Task<List<FetchRoomAmenityResponseDTO>> FetchRoomTypesByAmenityIdAsync(int amenityId)
        {
            var response = new List<FetchRoomAmenityResponseDTO>();

            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spFetchRoomTypesByAmenityID", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@AmenityID", amenityId);

            await connection.OpenAsync();
            using var reader = await command.ExecuteReaderAsync();
            while (await reader.ReadAsync())
            {
                response.Add(new FetchRoomAmenityResponseDTO
                {
                    RoomTypeID = reader.GetInt32(reader.GetOrdinal("RoomTypeID")),
                    TypeName = reader.GetString(reader.GetOrdinal("TypeName")),
                    Description = reader.GetString(reader.GetOrdinal("Description")),
                    AccessibilityFeatures = reader.GetString(reader.GetOrdinal("AccessibilityFeatures")),
                    IsActive = reader.GetBoolean(reader.GetOrdinal("IsActive"))
                });
            }

            return response;
        }

        //Add the Combination of Amenity Id and Room Type Id
        public async Task<RoomAmenityResponseDTO> AddRoomAmenityAsync(RoomAmenityDTO input)
        {
            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spAddRoomAmenity", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@RoomTypeID", input.RoomTypeID);
            command.Parameters.AddWithValue("@AmenityID", input.AmenityID);
            var statusParam = new SqlParameter("@Status", SqlDbType.Bit) { Direction = ParameterDirection.Output };
            var messageParam = new SqlParameter("@Message", SqlDbType.NVarChar, 255) { Direction = ParameterDirection.Output };
            command.Parameters.Add(statusParam);
            command.Parameters.Add(messageParam);

            await connection.OpenAsync();
            await command.ExecuteNonQueryAsync();

            return new RoomAmenityResponseDTO
            {
                IsSuccess = (bool)statusParam.Value,
                Message = (string)messageParam.Value
            };
        }

        //Delete the Combination of Amenity Id and Room Type Id
        public async Task<RoomAmenityResponseDTO> DeleteRoomAmenityAsync(RoomAmenityDTO input)
        {
            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spDeleteSingleRoomAmenity", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@RoomTypeID", input.RoomTypeID);
            command.Parameters.AddWithValue("@AmenityID", input.AmenityID);

            var statusParam = new SqlParameter("@Status", SqlDbType.Bit) { Direction = ParameterDirection.Output };
            var messageParam = new SqlParameter("@Message", SqlDbType.NVarChar, 255) { Direction = ParameterDirection.Output };
            command.Parameters.Add(statusParam);
            command.Parameters.Add(messageParam);

            await connection.OpenAsync();
            await command.ExecuteNonQueryAsync();

            return new RoomAmenityResponseDTO
            {
                IsSuccess = (bool)statusParam.Value,
                Message = (string)messageParam.Value
            };
        }

        //This will Perform Bulk Insert, i.e. one RoomTypeID with many AmenityIDs
        public async Task<RoomAmenityResponseDTO> BulkInsertRoomAmenitiesAsync(RoomAmenitiesBulkInsertUpdateDTO input)
        {
            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spBulkInsertRoomAmenities", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@RoomTypeID", input.RoomTypeID);
            command.Parameters.Add(CreateAmenityIDTableParameter(input.AmenityIDs));

            var statusParam = new SqlParameter("@Status", SqlDbType.Bit) { Direction = ParameterDirection.Output };
            var messageParam = new SqlParameter("@Message", SqlDbType.NVarChar, 255) { Direction = ParameterDirection.Output };
            command.Parameters.Add(statusParam);
            command.Parameters.Add(messageParam);

            await connection.OpenAsync();
            await command.ExecuteNonQueryAsync();

            return new RoomAmenityResponseDTO
            {
                IsSuccess = (bool)statusParam.Value,
                Message = (string)messageParam.Value
            };
        }

        //This will Perform Bulk Update, i.e. one RoomTypeID with many AmenityIDs
        public async Task<RoomAmenityResponseDTO> BulkUpdateRoomAmenitiesAsync(RoomAmenitiesBulkInsertUpdateDTO input)
        {
            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spBulkUpdateRoomAmenities", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@RoomTypeID", input.RoomTypeID);
            command.Parameters.Add(CreateAmenityIDTableParameter(input.AmenityIDs));

            var statusParam = new SqlParameter("@Status", SqlDbType.Bit) { Direction = ParameterDirection.Output };
            var messageParam = new SqlParameter("@Message", SqlDbType.NVarChar, 255) { Direction = ParameterDirection.Output };
            command.Parameters.Add(statusParam);
            command.Parameters.Add(messageParam);

            await connection.OpenAsync();
            await command.ExecuteNonQueryAsync();

            return new RoomAmenityResponseDTO
            {
                IsSuccess = (bool)statusParam.Value,
                Message = (string)messageParam.Value
            };
        }

        //Delete All Room Amenities By Room Type ID
        public async Task<RoomAmenityResponseDTO> DeleteAllRoomAmenitiesByRoomTypeIDAsync(int roomTypeId)
        {
            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spDeleteAllRoomAmenitiesByRoomTypeID", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@RoomTypeID", roomTypeId);

            var statusParam = new SqlParameter("@Status", SqlDbType.Bit) { Direction = ParameterDirection.Output };
            var messageParam = new SqlParameter("@Message", SqlDbType.NVarChar, 255) { Direction = ParameterDirection.Output };
            command.Parameters.Add(statusParam);
            command.Parameters.Add(messageParam);

            await connection.OpenAsync();
            await command.ExecuteNonQueryAsync();

            return new RoomAmenityResponseDTO
            {
                IsSuccess = (bool)statusParam.Value,
                Message = (string)messageParam.Value
            };
        }

        //Delete All RoomAmenities By Amenity ID
        public async Task<RoomAmenityResponseDTO> DeleteAllRoomAmenitiesByAmenityIDAsync(int amenityId)
        {
            using var connection = _connectionFactory.CreateConnection();
            using var command = new SqlCommand("spDeleteAllRoomAmenitiesByAmenityID", connection)
            {
                CommandType = CommandType.StoredProcedure
            };
            command.Parameters.AddWithValue("@AmenityID", amenityId);

            var statusParam = new SqlParameter("@Status", SqlDbType.Bit) { Direction = ParameterDirection.Output };
            var messageParam = new SqlParameter("@Message", SqlDbType.NVarChar, 255) { Direction = ParameterDirection.Output };
            command.Parameters.Add(statusParam);
            command.Parameters.Add(messageParam);

            await connection.OpenAsync();
            await command.ExecuteNonQueryAsync();

            return new RoomAmenityResponseDTO
            {
                IsSuccess = (bool)statusParam.Value,
                Message = (string)messageParam.Value
            };
        }

        // Helper method to create a SQL parameter for table-valued parameters
        private SqlParameter CreateAmenityIDTableParameter(IEnumerable<int> amenityIds)
        {
            var table = new DataTable();
            table.Columns.Add("AmenityID", typeof(int));
            foreach (var id in amenityIds)
            {
                table.Rows.Add(id);
            }

            var param = new SqlParameter
            {
                ParameterName = "@AmenityIDs",
                SqlDbType = SqlDbType.Structured,
                Value = table,
                TypeName = "AmenityIDTableType"
            };
            return param;
        }
    }
}
Explanation of Each Method:
  • FetchRoomAmenitiesByRoomTypeIdAsync: This method retrieves all amenities available for a specific room type. This can be used in various scenarios, such as displaying available amenities for each room type on a booking site or for administrative purposes in managing room features.
  • FetchRoomTypesByAmenityIdAsync: This method fetches all room types that offer a specific amenity. It is useful for understanding which room types offer certain amenities and can assist in operational planning and marketing strategies.
  • AddRoomAmenityAsync: This method adds a new association between a room type and an amenity. This function is important for updating the amenities list of a room type, ensuring that the room descriptions remain accurate and up-to-date.
  • DeleteRoomAmenityAsync: This method removes a specific amenity from a room type. This is important for maintaining accurate room descriptions and offerings, especially when amenities are unavailable or room configurations change.
  • BulkInsertRoomAmenitiesAsync: This method enables the addition of multiple amenities to a specific room type in a single operation. This is useful for setting up new room types or updating existing rooms with multiple new features at once.
  • BulkUpdateRoomAmenitiesAsync: This method updates the entire set of amenities for a given room type. It first clears existing amenities and then adds the new set, ensuring the room type’s amenity list is completely refreshed.
  • DeleteAllRoomAmenitiesByRoomTypeIDAsync and DeleteAllRoomAmenitiesByAmenityIDAsync: These methods facilitate the removal of all amenities associated with a specific room type or the removal of a specific amenity from all room types. They are essential for large-scale changes or discontinuations.
Register RoomAmenity Repository:

Next, we need to register the RoomAmenityRepository into the dependency injection container so that the framework can inject the object wherever we need this repository instance, mostly in the Controller class. So, please add the following code to the Program.cs class file:

builder.Services.AddScoped<RoomAmenityRepository>();

Creating RoomAmenity Controller:

Let us create the Room Amenity Controller and use the methods defined in the above RoomAmenityRepository class. So, create a new Empty API Controller named RoomAmenityController within the Controllers folder and copy and paste the following code.

using HotelBookingAPI.DTOs.RoomAmenityDTOs;
using HotelBookingAPI.Models;
using HotelBookingAPI.Repository;
using Microsoft.AspNetCore.Mvc;
using System.Net;

namespace HotelBookingAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class RoomAmenityController : ControllerBase
    {
        private readonly RoomAmenityRepository _roomAmenityRepository;
        private readonly ILogger<RoomAmenityController> _logger;

        public RoomAmenityController(RoomAmenityRepository roomAmenityRepository, ILogger<RoomAmenityController> logger)
        {
            _roomAmenityRepository = roomAmenityRepository;
            _logger = logger;
        }

        [HttpGet("FetchAmenitiesByRoomTypeId/{roomTypeId}")]
        public async Task<APIResponse<List<AmenityResponseDTO>>> FetchAmenitiesByRoomTypeId(int roomTypeId)
        {
            try
            {
                var amenities = await _roomAmenityRepository.FetchRoomAmenitiesByRoomTypeIdAsync(roomTypeId);
                if(amenities != null && amenities.Count > 0)
                {
                    return new APIResponse<List<AmenityResponseDTO>>(amenities, "Fetch Amenities By Room Type Id Successfully.");
                }

                return new APIResponse<List<AmenityResponseDTO>>(HttpStatusCode.BadRequest, "No Record Found");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error fetching amenities by room type ID");
                return new APIResponse<List<AmenityResponseDTO>>(HttpStatusCode.InternalServerError, "Error fetching amenities by room type ID", ex.Message);
            }
        }

        [HttpGet("FetchRoomTypesByAmenityId/{amenityId}")]
        public async Task<APIResponse<List<FetchRoomAmenityResponseDTO>>> FetchRoomTypesByAmenityId(int amenityId)
        {
            try
            {
                var roomTypes = await _roomAmenityRepository.FetchRoomTypesByAmenityIdAsync(amenityId);
                if (roomTypes != null && roomTypes.Count > 0)
                {
                    return new APIResponse<List<FetchRoomAmenityResponseDTO>>(roomTypes, "Fetch Room Types By AmenityId Successfully.");
                }

                return new APIResponse<List<FetchRoomAmenityResponseDTO>>(HttpStatusCode.BadRequest, "No Record Found");
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error fetching room types by amenity ID");
                return new APIResponse<List<FetchRoomAmenityResponseDTO>>(HttpStatusCode.InternalServerError, "Error fetching room types by amenity ID", ex.Message);
            }
        }

        [HttpPost("AddRoomAmenity")]
        public async Task<APIResponse<RoomAmenityResponseDTO>> AddRoomAmenity([FromBody] RoomAmenityDTO input)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, "Invalid Data in the Request Body");
                }

                var response = await _roomAmenityRepository.AddRoomAmenityAsync(input);
                if (response.IsSuccess)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(response, response.Message);
                }
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, response.Message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error adding room amenity");
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.InternalServerError, "Error adding room amenity", ex.Message);
            }
        }

        [HttpPost("DeleteRoomAmenity")]
        public async Task<APIResponse<RoomAmenityResponseDTO>> DeleteRoomAmenity([FromBody] RoomAmenityDTO input)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, "Invalid Data in the Request Body");
                }

                var response = await _roomAmenityRepository.DeleteRoomAmenityAsync(input);
                if (response.IsSuccess)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(response, response.Message);
                }
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, response.Message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error deleting room amenity");
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.InternalServerError, "Error deleting room amenity", ex.Message);
            }
        }

        [HttpPost("BulkInsertRoomAmenities")]
        public async Task<APIResponse<RoomAmenityResponseDTO>> BulkInsertRoomAmenities([FromBody] RoomAmenitiesBulkInsertUpdateDTO input)
        {
            try
            {
                if (!ModelState.IsValid)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, "Invalid Data in the Request Body");
                }

                var response = await _roomAmenityRepository.BulkInsertRoomAmenitiesAsync(input);
                if (response.IsSuccess)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(response, response.Message);
                }
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, response.Message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error performing bulk insert of room amenities");
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.InternalServerError, "Error performing bulk insert of room amenities", ex.Message);
            }
        }

        [HttpPost("BulkUpdateRoomAmenities")]
        public async Task<APIResponse<RoomAmenityResponseDTO>> BulkUpdateRoomAmenities([FromBody] RoomAmenitiesBulkInsertUpdateDTO input)
        {
            try
            {
                var response = await _roomAmenityRepository.BulkUpdateRoomAmenitiesAsync(input);
                if (response.IsSuccess)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(response, response.Message);
                }
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, response.Message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error performing bulk update of room amenities");
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.InternalServerError, "Error performing bulk update of room amenities", ex.Message);
            }
        }

        [HttpPost("DeleteAllRoomAmenitiesByRoomTypeID/{roomTypeId}")]
        public async Task<APIResponse<RoomAmenityResponseDTO>> DeleteAllRoomAmenitiesByRoomTypeID(int roomTypeId)
        {
            try
            {
                var response = await _roomAmenityRepository.DeleteAllRoomAmenitiesByRoomTypeIDAsync(roomTypeId);
                if (response.IsSuccess)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(response, response.Message);
                }
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, response.Message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error deleting all room amenities by room type ID");
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.InternalServerError, "Error deleting all room amenities by room type ID", ex.Message);
            }
        }

        [HttpPost("DeleteAllRoomAmenitiesByAmenityID/{amenityId}")]
        public async Task<APIResponse<RoomAmenityResponseDTO>> DeleteAllRoomAmenitiesByAmenityID(int amenityId)
        {
            try
            {
                var response = await _roomAmenityRepository.DeleteAllRoomAmenitiesByAmenityIDAsync(amenityId);
                if (response.IsSuccess)
                {
                    return new APIResponse<RoomAmenityResponseDTO>(response, response.Message);
                }
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.BadRequest, response.Message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error deleting all room amenities by amenity ID");
                return new APIResponse<RoomAmenityResponseDTO>(HttpStatusCode.InternalServerError, "Error deleting all room amenities by amenity ID", ex.Message);
            }
        }
    }
}
Explanation of the Controller Action Methods:
  • FetchAmenitiesByRoomTypeId: Retrieves a list of all amenities available for a specific room type identified by roomTypeId. This method is useful for front-end applications to display the amenities available for a specific type of room, enhancing customer information and aiding in room selection.
  • FetchRoomTypesByAmenityId: This endpoint fetches all room types that provide a specified amenity, identified by amenityId. It helps users understand which room types offer a specific amenity, which is useful for customer queries and administrative purposes.
  • AddRoomAmenity: This endpoint adds a new amenity to a room type. It takes a RoomAmenityDTO object containing RoomTypeID and AmenityID. This endpoint is essential for hotel staff to update or add new amenities to rooms, facilitating dynamic updates to room features.
  • DeleteRoomAmenity: This endpoint removes a specific amenity from a room type. It is crucial to keep the amenity list up-to-date if certain features are no longer offered. It also allows for removing outdated or unavailable amenities from room descriptions, ensuring accuracy in guest information.
  • BulkInsertRoomAmenities: Performs a bulk insertion of amenities for a specified room type. This is particularly useful when setting up a new room type or extensively updating an existing one. Streamlines the process of adding multiple amenities to a room type at once, enhancing efficiency.
  • BulkUpdateRoomAmenities: Updates all amenities for a given room type with a new set of amenities. This method essentially resets the amenities for a room type. Useful when there is a need to completely overhaul the amenities provided by a specific room type, such as during renovations or upgrades.
  • DeleteAllRoomAmenitiesByRoomTypeID: This endpoint deletes all amenities associated with a given room type. It is triggered by providing a roomTypeId. This is important for scenarios where a room type is being repurposed, or amenities are being broadly revised.
  • DeleteAllRoomAmenitiesByAmenityID: This endpoint removes a specific amenity from all room types where it is currently available. It is initiated by specifying an amenityId. This operation is vital for removing amenities that are phased out or no longer supported across all room types.

In this article, I explain the Room Amenities Module of our Hotel Booking Application. I hope you enjoy this implementation of the Room Amenities Module of a Hotel Booking Application article. Please take a few minutes to give your valuable feedback about this article. Your feedback means a lot to me and motivates me to give better examples and explanations.

What Next? Implementing the Hotel Search Module of the Hotel Booking Application.

Leave a Reply

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