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
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
- Building a Hotel Management System With ASP.NET Core(#1) - Introduction
- Building a Hotel Management System With ASP.NET Core(#2) - Building the Service Layer
- Building a Hotel Management System With ASP.NET Core(#3) - Building the RoomType Controller Logic
- Building a Hotel Management System With ASP.NET Core(#4) - Building the Room Controller Logic
- Building a Hotel Management System With ASP.NET Core(#5) - Complex Data Relationships(ManyToMany Relationship Linking)
- Building a Hotel Management System With ASP.NET Core(#6) - Displaying and Updating Related Data
- Building a Hotel Management System With ASP.NET Core(#7) - Working With Images
- Building a Hotel Management System With ASP.NET Core(#8) - Working with Jquery and AJAX
Proof of Work Done
Github Repo for the tutorial solution:
https://github.com/Johnesan/TheHotelApplication
Thank you for your contribution.
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!
Click here to view your Board of Honor
Congratulations @johnesan! You received a personal award!
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!