Building a Hotel Management System With ASP.NET Core(#9) - Storing and Displaying Room Images

in #utopian-io3 years ago

Repository

https://github.com/dotnet/core

What Will I Learn?

  • You will learn how to display related data of one entity in another.
  • You will learn how to Edit/Update related data of one entity in another.
  • You will learn how to store and display Selected Images of a Room in our Hotel Management System
  • You will make use of HashSets in C#
  • You will learn to use View Models

Requirements

  • Basic knowledge of C# programming language
  • Visual Studio 2017/ VS code/ Any suitable code editor
  • Previous Tutorial

Difficulty

  • Intermediate

Tutorial Contents

img8.PNG

In the previous tutorial, we learnt how to manage images on the front end. We learnt how to select images for a room when creating or editing them, preview them and remove those we didn't like. However, all these actions were all taking place in the client's browser. Like every real world application, these changes should be persisted in a database. In this tutorial, we are going to write the server side code that handles this process and also write code to display this related data when requested.

ImageItem Model

Room and Image are two distinct entities related in a ManyToMany relationship style, and as such there is no direct way of storing this relationship using a foreign key. We would require a new table. For that purpose, we create a new model ItemImage

public class ItemImage
    {
        public string ImageID { get; set; }
        public virtual Image Image { get; set; }
        public string ItemID { get; set; }
    }

I have called the model ItemImage because, despite working with the Room Entity at the moment, at some point we might want to create relationships between images and other model entities. It would be highly redundant to then create new models for each of them later on.

This model contains two foreign keys for the item in question, and the related image, as well as a navigation property for the image.

Adding an Itemimage DbSet

Now to interact with the ItemImage table, we need a DbSet property defined in our db context

  • ApplicationDbContext.cs
public DbSet<ItemImage> ItemImageRelationships { get; set; }

Using FluentAPI, we define the primary key for the table as a combination of the two foreign key properties, by overriding the OnModelCreating method

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

            builder.Entity<ItemImage>()
                .HasKey(x => new { x.ItemID, x.ImageID });
        }

Updating Our Database

Now that we have made changes to our ApplicationDbContext, we need to effect those changes on our database.

  • Add a Migration
Add-Migration RoomFeatureRelationship
  • Then run
Update-Database

Now check your database, If everything went well, you should see the new table ItemImage and the appropriate Foreign keys for the Joined Entities

UpdateRoomImagesList Method

Now that we have created the ItemImage table in our db, let us add a method to handle the updating of the selected room images in our GenericHotelService class. To do that, as usual, we add the method signature definition in our IGenericHotelService interface

  • IGenericHotelService.cs
void UpdateRoomImagesList(Room room, string[] imageIDs);

This method definition will take in two parameters - the room to be updated and a string array of imageIDs.


Next we implement this in our GenericHotelService.cs class:

  • GenericHotelService.cs
 public void UpdateRoomImagesList(Room room, string[] imagesIDs)
        {
            var PreviouslySelectedImages = _context.ItemImageRelationships.Where(x => x.ItemID == room.ID);
            _context.ItemImageRelationships.RemoveRange(PreviouslySelectedImages);
            _context.SaveChanges();

            if (imagesIDs != null)
            {
                foreach (var imageID in imagesIDs)
                {                    
                    try
                    {
                        var AllImagesIDs = new HashSet<string>(_context.Images.Select(x => x.ID));
                        if (AllImagesIDs.Contains(imageID))
                        {
                            _context.ItemImageRelationships.Add(new ItemImage
                            {
                                ImageID = imageID,
                                ItemID = room.ID
                            });
                        }
                    }     
                    catch(Exception e)
                    {
                        continue;
                    }
                }
                _context.SaveChanges();
            }
        }

Explanation for Code Above:

The first statement gets the previously linked Features to the room and thereafter removes those relationship from the RoomFeatureRelationships Table then call SaveChanges().

 var PreviouslySelectedImages = _context.ItemImageRelationships.Where(x => x.ItemID == room.ID);
            _context.ItemImageRelationships.RemoveRange(PreviouslySelectedImages);
            _context.SaveChanges();

  • Next, we check if the imageIDs array passed in is null. If not, we loop through the image IDs in the array, check our Images DbSet to ascertain if it is a valid ID linked to an image in the database, if yes, we create a relationship between the imageID and the item(room) ID. Thereafter, we SaveChanges() to persist our changes to the database.
 if (imagesIDs != null)
            {
                foreach (var imageID in imagesIDs)
                {                    
                    try
                    {
                        var AllImagesIDs = new HashSet<string>(_context.Images.Select(x => x.ID));
                        if (AllImagesIDs.Contains(imageID))
                        {
                            _context.ItemImageRelationships.Add(new ItemImage
                            {
                                ImageID = imageID,
                                ItemID = room.ID
                            });
                        }
                    }     
                    catch(Exception e)
                    {
                        continue;
                    }
                }
                _context.SaveChanges();

Create POST Action

Back at our create action in the RoomController, we need a way to get the image IDs selected, and pass them to this service method we just defined. Edit the Create post action and add the following code

   [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Number,RoomTypeID,Price,Available,Description,MaximumGuests")] Room room, string[] SelectedFeatureIDs, string[] imageIDs)
        {            
            if (ModelState.IsValid)
            {
              .
              .
              .
   _hotelService.UpdateRoomImagesList(room, imageIDs);
            }
            .
            .
            .
      }

Notice how we have string[] imageIDs as part of the parameters for this method. This catches the value of all input elements with the name imageIDs.
We then pass this array of image IDs to the _hotelService `UpdateRoomImagesList method.

*Try to create a new room and select a list of images, then check the ItemImage table to confirm the additions.

Displaying Room Related Data

Now that we have successfully added selected images for a room, let us display these images for the room in its details page.

  • Create RoomFeaturesAndImagesViewModel

To present this data as one model to the view, for display, we need a ViewModel. Recall that the Room entity has two foreign relationships with the Feature and Image entities. To fetch and return these related data to the view, we make use of a viewModel that has two properties, each of these entities.

    public class RoomFeaturesAndImagesViewModel
    {
        public List<Image> Images { get; set; }
        public List<Feature> Features { get; set; }
    }

Now lets add a method in our service class to handle this fetch process. Add the method signature definition in our IGenericHotelService interface

  • IGenericHotelService.cs
 Task<RoomFeaturesAndImagesViewModel> GetRoomFeaturesAndImagesAsync(Room room);

This method definition takes in a room as parameter and returns a RoomFeaturesAndImagesViewModel type.

Next we implement this in our GenericHotelService.cs class:

  • GenericHotelService.cs
  public async Task<RoomFeaturesAndImagesViewModel> GetRoomFeaturesAndImagesAsync(Room room)
        {
            var RoomImagesRelationship =  _context.ItemImageRelationships.Where(x => x.ItemID == room.ID);
            var images = new List<Image>();
            foreach(var RoomImage in RoomImagesRelationship)
            {
                var Image = await _context.Images.FindAsync(RoomImage.ImageID);
                images.Add(Image);
            }


            var RoomFeaturesRelationship = _context.RoomFeatureRelationships.Where(x => x.RoomID == room.ID);
            var features = new List<Feature>();
            foreach(var RoomFeature in RoomFeaturesRelationship)
            {
                var Feature = await _context.Features.FindAsync(RoomFeature.FeatureID);
                features.Add(Feature);
            }

            var ImagesAndFeatures = new RoomFeaturesAndImagesViewModel
            {
                Images = images,
                Features = features
            };
            return ImagesAndFeatures;
        }

Explanation For the Code Above

First, we fetch all the rows from the ItemImagesRelationship table with roomID of that supplied as argument to the method. Thereafter we create a list of images. Iterate through each Image from the selected RoomImages rows and add them to this list.

var RoomImagesRelationship =  _context.ItemImageRelationships.Where(x => x.ItemID == room.ID);
            var images = new List<Image>();
            foreach(var RoomImage in RoomImagesRelationship)
            {
                var Image = await _context.Images.FindAsync(RoomImage.ImageID);
                images.Add(Image);
            }


We do the same for features, only this time, fetching from the RoomFeatureRelationships table.


 var RoomFeaturesRelationship = _context.RoomFeatureRelationships.Where(x => x.RoomID == room.ID);
            var features = new List<Feature>();
            foreach(var RoomFeature in RoomFeaturesRelationship)
            {
                var Feature = await _context.Features.FindAsync(RoomFeature.FeatureID);
                features.Add(Feature);
            }


Lastly, we add create a new RoomFeaturesAndImagesViewModel and set the properties appropriately to these list of images and features. Then we return this ViewModel.

  var ImagesAndFeatures = new RoomFeaturesAndImagesViewModel
            {
                Images = images,
                Features = features
            };
            return ImagesAndFeatures;
        }

Details Action

Back at our controller class, edit the Details action as follows:

 public async Task<IActionResult> Details(string id)
        {
           .
           .
           .
            var ImagesAndFeatures = await _hotelService.GetRoomFeaturesAndImagesAsync(room);
            ViewData["Features"] = ImagesAndFeatures.Features;
            ViewData["Images"] = ImagesAndFeatures.Images;
            return View(room);
        }
  • Here, we make a call to the GetRoomFeaturesAndImagesAsync() that we just defined. We then send the features and images as ViewData to the view.
  • Details View

We edit our details view to capture and reflect these new data.

>
<h4>Features</h4>
@foreach (var feature in ViewBag.Features as IEnumerable<Feature>)
{
    <p>@feature.Name</p>
    <p>@feature.Icon</p>
}

<h4>Images</h4>
@foreach (var image in ViewBag.Images as IEnumerable<Image>)
{
    <lmg src="@image.ImageUrl" asp-append-version="true" alt="@image.Name"/>
}


Curriculum

Proof of Work Done

Github Repo for the tutorial solution:
https://github.com/Johnesan/TheHotelApplication

Sort:  

Thank you for your contribution.

  • Please enter the source of the first image.
  • Put some comments on your code, plus the explanation you've already made.

It would be interesting to see a backend for upload image of each room and description.

Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.

To view those questions and the relevant answers related to your post, click here.


Need help? Write a ticket on https://support.utopian.io/.
Chat with us on Discord.
[utopian-moderator]

Hey @johnesan
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!

Want to chat? Join us on Discord https://discord.gg/h52nFrV.

Vote for Utopian Witness!

Lol I was planning to do the same!! It's great that you have started it :) Now i can have some guidance where to start!

Congratulations @johnesan! You received a personal award!

1 Year on Steemit

Click here to view your Board of Honor

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @johnesan! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!