Small hotels may want to show their room availability for guests to book. We'll look at a real-world implementation of how this can be achieved using a Minimal WebApi in C#.
Introduction
Imagine you manage a small hotel called The Quirky Koala Inn. You've several rooms that you can book. One of the many phone requests you receive is if a particular room is available for booking. These requests can take time, so you want to limit the number of phone requests unless it is a reservation. So you would like to show potential guests the room availability on your website, app or third-party website.
In this article, we're going to walk through how to implement a Minimal WebApi for The Quirky Koala Inn. In subsequent articles, we'll extend the features and show how the WebApi can be connected to an MVC website and elsewhere.
Background
I've spent the last few years working on 'Sticky Guest', a platform that hotel managers can use to manage reservations, guests, accounts, etc. Not all hotels would need anything as complex, and here is a way that any hotel owner can use to build simple functions to improve their guests' experience.
Requirements
We've been tasked with solving this issue for The Quirky Koala Inn. After discussions with the manager, we've come up with the following requirements:
- Create a database of room availability.
- Create a WebApi service to access the availability.
- Create a website or app to use the WebApi to show the potential guests the room availability. (not covered in Part 1)
- Create a management portal so that room availability can be adjusted (not covered in Part 1)
I've attached a ZIP of the code to this article. You can unZIP and open it in Visual Studio 2022 or code along.
Part 1. Building a WebApi Endpoint
We'll be using Visual Studio 2022 (Edit - CodeProject doesn't allow external links so I've removed this. You can find the download by using a search engine and searching for "Visual Studio 2022 download". This should take you to the Microsoft website. This shouldn't be confused with "Visual Studio Code" that is a different product and will not work for this tutorial!) to create the project, and a free and paid version is available from the Microsoft website.
Gotcha - make sure during the installation of Visual Studio 2022, you select the ASP.NET and web development workload. Otherwise, you'll be missing options that we'll be using.
1.1 Create the Solution and WebApi Project
By the time we've finished, we'll have several Projects within the single Solution. It's easiest to set this up initially, otherwise, you'll have to rename projects, namespaces and variables later. A Solution consists of many related Projects.
Start Visual Studio 2022 and select File > Create a new Project.
Search for Blank Solution and click Next.
Under Configure your new project, enter the Solution name as TheQuirkyKoalaInn
and click Create.
A new empty Solution will be created.
Visual Studio 2022 has no option File > Create a new Solution, so we must use this workaround.
We'll now create the WebApi and supporting Projects.
Right-click on the Solution in the Solution Explorer, and select Add New Project. In the search box, enter 'ASP.NET Core Empty', choose the project template and click Next.
In the configuration, enter the Project name as TheQuirkyKoalaInnWebApi
. Make sure that the Location is in the subfolder of the Solution as TheQuirkyKoalaInn\TheQuirkyKoalaInn\TheQuirkyKoalaInnWebApi. This will become important as more projects are added. Click Next.
Under Additional information, select the Framework '.NET 7' and click Create. The project will then be created.
Check that the project runs by selecting Debug > Start Debugging or pressing F5. After the project compiles, it should launch a browser that displays 'Hello World!'
Gotcha - Depending on your settings in Visual Studio, it may prompt if you want to trust the pre-built SSL Certificate. You'd want to click Yes and Yes to install the security certificate locally. If you later deploy the project to a server, we'd expect the server to have an SSL certificate allowing encrypted calls via HTTPS rather than HTTP. We don't need to concern ourselves with this until we deploy the website so the public can access it.
Close the browser, and the project will stop running.
If we open the Program.cs file that was created in the project, we'll find this:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.Run();
The line:
app.MapGet("/", () => "Hello World!");
This shows us that when we navigate to "/"
of the website, we'll execute this code. In this example, we're returning a string
"Hello World!"
. We want to add a new entry so that we return our room availability calendar when we navigate to it. Let's change the code as:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Hello World!");
app.MapGet("/calendar", () =>
{
return "todo calendar";
});
app.Run();
Now when we launch the application (F5) and navigate to the /calendar, we'll get "todo calendar". This is where we'll be putting our new code.
Both the "/"
and "/calendar"
are called endpoints. We'd add a new endpoint each time we want to add a new function. We call it an endpoint because it's the starting point for an end client. If we reverse that and look at it from the API's perspective, it's a point at the end of the code that can be accessed. The lesson here is that it's hard to name things.
We now need some way to store the room availability to show it to the guest. We'd do that with a database and model.
1.2 Build the Database and Model
We want to store the room availability that the hotel manager has set. Then when a guest navigates to the website, that information can be retrieved and displayed. For that, we are going to create a simple database. The database stores a representation of a room and its availability. We can't keep the actual room in the database, so instead, we hold a representation of the room, which we call a Model. A Model represents something in the real world.
We're going to create a new project for this database and model. This allows us to use it in other projects later without having to copy all the code. We want to avoid having multiple copies of the same code.
Right-click on the Solution and select Add > New Project, as we did previously. Select the Class Library (.NET or .NET Framework) option (not to be confused with Class Library (.NET Framework), which we don't want here).
Click Next.
For the Configure your new project, enter the Project name as TheQuirkyKoalaInnDbClassLibrary
and ensure that the subfolder is set to TheQuirkyKoalaInn\TheQuirkyKoalaInn\TheQuirkyKoalaInnDbClassLibrary.
It's worth noting the folder structure here, as we have the main Solution in the main folder. Then each Project is in its own subfolder. This is helpful as it allows us to reuse Projects and keep them simple. If we start using Source Control, which backs up our changes, amongst other things, then we'll need each Project in its own subfolder beneath the main project.
The boundary of where a Project exists is not always clear as it's context dependent. In this situation, we know that we want to use the Database Project (TheQuirkyKoalaInnDbClassLibrary)
in multiple other Projects, so it has to be in its own separate Project.
Click Next. Under Additional information, choose .NET 7 and click Create to create the Project.
The project will be created with a default empty class called Class1.cs. We'll be making our own class, so right click on the Class1.cs file in the Solution Explorer and choose Delete from the menu.
Data Transfer Object (DTO)
We will also need another Project to hold our Data Transfer Objects (DTOs). These objects are used to send data to and from the client. We'd have to send the database objects to the client without these objects. This can cause a security issue, exposing additional information to the client that we may not want to send. It also simplifies the code for the client as they won't need to worry about the database's internal structure.
Right-click on the Solution and select Add > New Project, as we did previously. Select the Class Library (.NET or .NET Framework) option.
For the Configure your new project, enter the Project name as TheQuirkyKoalaInnDtoClassLibrary
and ensure that the subfolder is set to TheQuirkyKoalaInn\TheQuirkyKoalaInn\TheQuirkyKoalaInnDtoClassLibrary.
Create the Project with the same settings as before and delete the default Class1.cs.
You should end with a Solution looking like this:
Adding the Db Code
We'll now create the code for the Projects.
We want to model a Room
, so let's create that. Right-click on the TheQuirkyKoalaInnDbClassLibrary
Project, and select Add > Class. Enter the name of the Class as Room.cs and click Add.
Replace the default created code with:
namespace TheQuirkyKoalaInnDbClassLibrary
{
public class Room
{
public string? Name { get; set; }
public string? Availability { get; set; }
}
}
The Name
will become the name of the room.
The Availability
will represent a series of characters to define whether the room is available for a period of dates. A "1" will represent an open date, while a "0" will mean the room is booked. There are other ways of representing Availability
here. For instance, we could use an array of Booleans. But the string
will make it easier to see what is happening and make it easier to save to a real database should we wish to later. This is something of a personal choice.
We'll also need a class to store all the Rooms as we'll have more than one. We'll also want a class to store all of these objects. So we'll create a new Db
class for both of these. Right-click on the TheQuirkyKoalaInnDbClassLibrary
project, and select Add > Class. Enter the name of the Class as Db.cs and click Add.
Replace the default created code with the following:
namespace TheQuirkyKoalaInnDbClassLibrary
{
public class Db
{
public List<Room> RoomList { get; set; }
public DateOnly AvailabilityStartDateData { get; set; }
public Db()
{
AvailabilityStartDateData = DateOnly.FromDateTime(DateTime.Now);
RoomList = new List<Room>
{
new Room()
{
Name = "Room 1 - Double",
Availability = "1100111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
111111111111111111111111"
},
new Room()
{
Name = "Room 2 - Double",
Availability = "1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
111111111111111111111111"
},
new Room()
{
Name = "Room 3 - Executive",
Availability = "1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
111111111111111111111111"
},
new Room()
{
Name = "Room 3 - Single",
Availability = "1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
1111111111111111111111111111111111111111111111111111
111111111111111111111111"
}
};
}
}
}
The List<Room> RoomList
gives us a list of several rooms. We'll be creating three for our hotel.
The DateOnly AvailabilityStartDateData
will tell us the starting date for the room's availability. If this is set to May 1st, then the first character in the Availability
will be for May 1st, the second character for May 2nd, etc.
The constructor public Db()
creates the hotel rooms and populates some availability test data. When we launch the application, this will be automatically called, and the data will be populated and ready for us to use.
Adding the Dto Code
We'll now create the Dto
Project (TheQuirkyKoalaInnDtoClassLibrary
) code.
We want to be able to create a Dto
object from a Database Room model object. By default, a Project doesn't know about another Project. If we try and reference a class in the TheQuirkyKoalaInnDbClassLibrary
Project, it will generate an error:
The type or namespace name 'XYZ' could not be found (are you missing a using directive or an assembly reference?).
We need to add a reference to the related Project to fix this. To do that, right-click the TheQuirkyKoalaInnDtoClassLibrary
Project, and select Add > Project Reference.
The Reference Manager will appear, check the TheQuirkyKoalaInnDbClassLibrary
Project and click OK.
The TheQuirkyKoalaInnDtoClassLibrary
Project will now be able to access any classes marked as Public
from the TheQuirkyKoalaInnDbClassLibrary
Project.
We'll need a RoomDto
, so let's create that. Right-click on the TheQuirkyKoalaInnDtoClassLibrary
Project, and select Add > Class. Enter the name of the Class as RoomDto.cs and click Add.
Replace the default created code with:
using TheQuirkyKoalaInnDbClassLibrary;
namespace TheQuirkyKoalaInnDtoClassLibrary
{
public class RoomDto
{
public string? Name { get; set; }
public string? Availability { get; set; }
public RoomDto() { }
public RoomDto(Room room, DateOnly availabilityStartDate, DateOnly getFromDate)
{
if (String.IsNullOrEmpty(room.Availability))
throw new ArgumentNullException(nameof(room.Availability));
int startIndex =
(int)(getFromDate.DayNumber - availabilityStartDate.DayNumber);
if (startIndex > 365 - 30) startIndex = 365 - 30;
if (startIndex < 0) startIndex = 0;
this.Name = room.Name;
this.Availability = room.Availability.Substring(startIndex, 30);
}
}
}
The Name
is the name of the room as previously for the database.
The Availability
is the same string
as before, except that the starting date will depend on the hotel guest's needs. For example, our database might store room availability from May 1st, but our guests might want to look at May 14th. We don't want to transfer the entire availability to the client device, so we'll transfer from May 14th.
The code here is of interest:
public RoomDto(Room room, DateOnly availabilityStartDate, DateOnly getFromDate)
This creates a RoomDto
object from a room
object. For this to happen, we also need to know what the starting date is for the room.Availability
(availabilityStartDate
) and what date range we want returned (getFromDate
). We'll then pass back an arbitrary 30 days of room availability.
This line allows us to reference the TheQuirkyKoalaInnDbClassLibrary
Project:
using TheQuirkyKoalaInnDbClassLibrary;
We'll give the guest the ability to retrieve many rooms at once. This is an assumption on our part, as it would depend on what the client app needs. When we have a Rooms, Dates and Availability collection, it seems reasonable to call that a Calendar
. We'll encapsulate this into a CalendarDto
object.
Let's create a CalendarDto
. Right-click on the TheQuirkyKoalaInnDtoClassLibrary
Project, and select Add > Class. Enter the name of the Class as CalendarDto.cs and click Add.
Replace the default created code with:
namespace TheQuirkyKoalaInnDtoClassLibrary
{
public class CalendarDto
{
public List<RoomDto>? RoomList { get; set; }
public DateOnly AvailabilityStartDateData { get; set; }
}
}
The List<RoomDto>? RoomList
is our list of Room
s with availability.
The DateOnly AvailabilityStartDateData
is the calendar's start date.
Now we need to link this up to the WebApi.
Adding the WebApi Code
With all that in place, we can now add the code to the WebApi project (TheQuirkyKoalaInnWebApi
) to return the calendar to the client.
The TheQuirkyKoalaInnWebApi
Project needs to use the code in the other two Projects. As before, we need to add Project references. To do that, right-click the TheQuirkyKoalaInnWebApi
Project, select Add > Project Reference. and check the TheQuirkyKoalaInnDtoClassLibrary
and TheQuirkyKoalaInnDbClassLibrary
Projects. Click OK to save.
Then edit the Program.cs file and replace the code with:
using TheQuirkyKoalaInnDbClassLibrary;
using TheQuirkyKoalaInnDtoClassLibrary;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<Db>();
var app = builder.Build();
app.MapGet("/calendar/{date?}", (DateOnly? date, Db db) =>
{
date = date ?? DateOnly.FromDateTime(DateTime.Now);
var cal = new CalendarDto
{
AvailabilityStartDateData = (DateOnly)date,
RoomList = new List<RoomDto>()
};
foreach (var room in db.RoomList)
{
cal.RoomList.Add(new RoomDto
(room, db.AvailabilityStartDateData, (DateOnly)date));
}
return cal;
});
app.MapGet("/", () =>
{
return "Hello World";
});
app.Run();
We're using Dependency Injection to create the Database (Db
) class instance with the following:
builder.Services.AddSingleton<Db>();
A Singleton says that we want a single instance of that class. Otherwise, we'd have multiple different database classes created. We might get away with this for now, as we're only reading the data. When we later want to save data, this would cause issues as to which of the many instances would we be saving it to? We wouldn't know. Imagine dropping a plate of spaghetti on the floor and then picking it up with chopsticks.
We create an endpoint for our hotel here, when we pass in a date
, we want to get back a CalendarDto
:
app.MapGet("/calendar/{date?}", (DateOnly? date, Db db) =>
The parameter Db db
is handled by the Dependency Injection that we defined above. If we created more endpoints, we would pass in the database similarly. The client doesn't need to worry or even know about this.
The ? in {date?}
indicates that the parameter is optional. If the client doesn't pass in a date, then we'll assume they want a calendar from today's date as:
date = date ?? DateOnly.FromDateTime(DateTime.Now);
Here we're creating an instance of the CalendarDto
ready to pass back:
var cal = new CalendarDto
{ AvailabilityStartDateData = (DateOnly)date, RoomList = new List<RoomDto>() };
The processing is done before being passed back to the client:
foreach (var room in db.RoomList)
{
cal.RoomList.Add(new RoomDto(room, db.AvailabilityStartDateData, (DateOnly)date));
}
Here, we're taking advantage of the Dto constructor method we created earlier, which converts a database Room into the RoomDto
.
Test the WebApi
If all is well, we should be able to test what we have. Since we're just using Get
methods in the API, we can test this in the browser. We'd need to use something like the Postman desktop app if we had more complex methods.
Press F5 to launch the app, then navigate to the calendar URL as https://localhost:7031/calendar.
By default, the data is displayed in JSON format. This is a human-readable format which makes it easier to see what is happening.
{
"roomList": [
{
"name": "Room 1 - Double",
"availability": "110011111111111111111111111111"
},
{
"name": "Room 2 - Double",
"availability": "111111111111111111111111111111"
},
{
"name": "Room 3 - Executive",
"availability": "111111111111111111111111111111"
},
{
"name": "Room 3 - Single",
"availability": "111111111111111111111111111111"
}
],
"availabilityStartDateData": "2023-05-04"
}
Here, we're calling the WebApi the same way as a client app would and having the room availability returned. This can be used in a website, app or any other service we might want to use.
In the next part, we'll set up a website to show it in action.
History
- 5th May, 2023 - Removed links to external websites to conform to CP article requirements.
- 4th May, 2023 - First draft complete, first edit complete, submitted
- 3rd May, 2023 - Started on the first draft
- 2nd May, 2023 - Coded