Repository
https://github.com/dotnet/core
What Will I Learn?
- You will learn how to create the application logic for the Room Controller of our Hotel System.
- You will learn about ViewBags/ViewData and how they are used to pass data between Controller and View.
- You will learn about ViewModels, why they are needed in building ASP.NET MVC applications and you will implement one.
- You will learn how to make use of a
SelectList
in ASP.NET MVC. - You will learn to perform controller CRUD operations in ASP.NET Core.
Requirements
- Basic knowledge of C# programming language
- Visual Studio 2017/ VS code/ Any suitable code editor
- Previous Tutorial
Difficulty
- Intermediate/Advanced
Tutorial Contents
In the previous tutorial, we began writing our application logic beginning with the RoomType which represents the room categories by which the rooms in our hotel are classified. In this tutorial, we will write the application logic for our room controller. Recall that these controllers are projections of our defined model entities.
Please read the previous tutorial post as we will be building on some concepts mentioned therein.
Creating Our Controller
Right Click on your Controller folder and Add a new controller with the following selected
Select the model class as Room and the Data class as ApplicationDbContext
(1) Index Action
The Index action of our room controller takes a different fashion from what we saw in the previous controller. In the RoomType
index view, we simply returned a list of the room types. However, in the Room Index view, our intention is to return a list of all the rooms present in our hotel as well as a list of all the room types so we can sort our rooms based on the room type category they belong to.
Passing Two Models to a View
To achieve the task stated above, we would need to supply to our view two distinct models. There are two ways we can achieve this in ASP.NET MVC and I will show both of them.
Using ViewBags/ViewData
All along we have been passing data from the controller to the view using strongly typed models. ViewBags in MVC are a way to loosely pass data from the controller to the view. We can embed anything in a viewbag and pass it to the view, from the controller. In the case of our system, we would need to define the context class so we can access the RoomTypes
DbSet.
Edit your controller as follows:
- Add an ApplicationDbContext property
private readonly ApplicationDbContext _context;
- Make this property a parameter in your controller constructor
public RoomsController(IGenericHotelService<Room> hotelService, ApplicationDbContext context)
{
_hotelService = hotelService;
_context = context;
}
Now in your index Action,
public async Task<IActionResult> Index()
{
ViewBag.RoomTypes = _context.RoomTypes.ToList();
return View(await _hotelService.GetAllItemsAsync());
}
Note that it is also possible to replace ViewBag.RoomTypes
with ViewData["RoomTypes"]
. They are just two different ways of writing the same thing
To view the result of what we just performed, run the solution in debug mode by pressing F5. Set a breakpoint at the line of the ViewBag and then navigate to /rooms/index
We notice that, hovering over the ViewBag variable or observing the Auto monitor, ViewBag.RoomTypes
contains three items in it, all of type RoomType
You can enable the auto window by clicking on Debug --> Windows --> Auto
- Now, in our view, all we need to access this list is to simply call
@ViewBag.RoomTypes
. We can iterate through the list by
@foreach(var roomType in ViewBag.RoomTypes as IEnumerable<RoomType>)
{
//Perform required action
}
It is obvious that this is a very easy and convenient method of passing data to and from the controller. However, this method has its many disadvantages. First off, it defeats the purpose for which we isolated the database fetching logic to a separate layer. As can be seen from the controller constructor, we now pass in the application context which the controller was never supposed to interact with directly. Also, we loose intelliscence when working with ViewBag content. We might then run into error scenarios where we can't pin point where exactly the error is coming from.
For these reasons, this method is not convenient to fetch the RoomType DbSet.
Using ViewModels
In this method, we create a ViewModel which contains two properties corresponding to a list of each of these models (Room and RoomType). A ViewModel is a model made specifically for use in a particular view.
- Create a New Folder -- ViewModels
- Create a new Class -- RoomsAdminIndexViewModel.cs and put in the following code
public class RoomsAdminIndexViewModel
{
public List<Room> Rooms { get; set; }
public List<RoomType> RoomTypes { get; set; }
}
Because what we want to achieve is beyond the scope of our GetAllItemsAsync()
method in the IGenericHotelService
implementation which returns only a list of a particular model, we would need to define a new method to handle this special FETCH
request to return two DbSets from the database. We do this as follows:
Add a New Method to IGenericHotelService
RoomsAdminIndexViewModel GetAllRoomsAndRoomTypes();
Implement this New Method in GenericHotelService
public RoomsAdminIndexViewModel GetAllRoomsAndRoomTypes()
{
var rooms = _context.Rooms.ToList();
var roomtypes = _context.RoomTypes.ToList();
var RoomsAdminIndeViewModel = new RoomsAdminIndexViewModel
{
Rooms = rooms,
RoomTypes = roomtypes
};
return RoomsAdminIndeViewModel;
}
Controller Method
Having done all these, in our controller method, we make a call to the service method.
public IActionResult Index()
{
return View(_hotelService.GetAllRoomsAndRoomTypes());
}
- When we debug now, with our breakpoint set, we find out that our model contains the required DbSets.
Updating Our View
Having altered the returning model, we have to update our view to reflect the changes.
First we change our model definition for the view
@model TheHotelApp.ViewModels.RoomsAdminIndexViewModel
Our table is updated as follows:
<table class="table">
<thead>
<tr>
<th>Number</th>
<th>Price</th>
<th>Availability</th>
<th>Description</th>
<th>Maximum Guests</th>
<th>Room Category</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model.Rooms)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Number)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Available)
</td>
<td>
@Html.DisplayFor(modelItem => item.Description)
</td>
<td>
@Html.DisplayFor(modelItem => item.MaximumGuests)
</td>
<td>
@Html.DisplayFor(modelItem => item.RoomType.Name)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
<a asp-action="Details" asp-route-id="@item.ID">Details</a> |
<a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
</td>
</tr>
}
</tbody>
</table>
- The most significant change from what was present previously is the array that we are now looping against
@foreach (var item in Model.Rooms)
To illustrate that RoomTypes DbSet is present, let us return a list of all the RoomType names:
@foreach (var item in Model.RoomTypes)
{
<div>
<p>@item.Name</p>
</div>
}
When we run our project and navigate to localhost:port/rooms
we get the anticipated result.
(2) Create Action
The Create action, just like the index action has a slight variation from the implementation in the RoomType controller.
Recall that the Room Entity has a one-one relationship with the RoomType. We need to provide a way for the user to select a RoomType entity that corresponds to this property. To achieve that, we need to provide the user with a list of all the RoomTypes and have him/her select one.
Just like the former, this result cannot be achieved with our standard defined Service Methods. Since we specified TEntity
as Room
, our GetAllItemsAsync()
can only fetch Room Entities and not RoomType Entities. In order to fetch RoomType entities, we need to create a new dedicated method for this.
Just like we did in the above,
Add a New Method to IGenericHotelService
Task<IEnumerable<RoomType>> GetAllRoomTypesAsync();
Implement this New Method in GenericHotelService
public async Task<IEnumerable<RoomType>> GetAllRoomTypesAsync()
{
return await _context.RoomTypes.ToArrayAsync();
}
Create Action Method Implementation
Having done all these, in our controller method, we make a call to the service method and pass it into a SelectList, which is sent down to the view via a ViewData.
public IActionResult Create()
{
var RoomTypes = _hotelService.GetAllRoomTypesAsync().Result;
ViewData["RoomTypeID"] = new SelectList(RoomTypes, "ID", "Name");
return View();
}
(3) Edit Action
- The Edit action just like the Create Action implements the
GetAllRoomTypesAsync()
service method to supply a ViewBag containing the List of RoomTypes to the view. - The
POST
implementation remains the same as that in the previous tutorial.
(4) Details Action
- We implement the details action just like we did in the previous tutorial on RoomType Controller. However, in subsequent tutorial, we are going to make changes to this method so we can display the list of foreign properties such as features and images associated with a particular room.
(5) Delete Action
- The Delete action again is similar to that done in the previous tutorial.
To prevent repetition and also to avoid making this post too cumbersome that you miss the main points, I would not write out the code for those actions. If you have trouble implementing any of them, do well to check the github repo for this tutorial to put yourself right back on track
https://github.com/Johnesan/TheHotelApplication/blob/master/TheHotelApp/Controllers/RoomsController.cs
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
Proof of Work Done
Github Repo for the tutorial solution:
https://github.com/Johnesan/TheHotelApplication
Thank you for your contribution.
While I liked the content of your contribution, I would still like to extend few advices for your upcoming contributions:
Looking forward to your upcoming tutorials.
Your contribution has been evaluated according to Utopian rules 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!
Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!