Building a Hotel Management System With ASP.NET Core(#3) - Building the RoomType Controller Logic

in #utopian-io3 years ago (edited)

Repository

https://github.com/dotnet/core

What Will I Learn?

  • You will learn how to create the application logic for the RoomType Controller of our Hotel System.
  • You will learn more about dependency injection with ASP.NET Core
  • You will learn to perform controller CRUD operations in ASP.NET Core
  • You will learn learn about Cross Site Request Forgery(CSRF) and how It is handled in ASP.NET Core using Anti Forgery Tokens.

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 created the service layer which handles all our interactions with the database. Now we will begin handling specific application logic beginning with the RoomTypeController . The reason we are beginning from this particular controller is because it has the least dependencies on other. By that I mean that the RoomType model has less connections with other models.

Right Click on your Controller folder and Add a new controller with the following selected
Screenshot (374).png

Capture.PNG

Select the model class as RoomType and the Data class as ApplicationDbContext
aCapture.PNG.

Injecting Our Service Class

Remember in the previous tutorial, we talked about dependency injection and why it was necessary to define an interface. Here in our controller class, we are going to make use of the interface's methods to handle our database interaction.

Delete the _context property and replace it with a _roomTypeService property like this:

private readonly IGenericHotelService<RoomType> _roomTypeService;

Now reimplement the constructor as follows:

public RoomTypesController(IGenericHotelService<RoomType> roomTypeService)
        {
            _roomTypeService = roomTypeService;
        }
  • Notice how we have now replaced the "TEntity" class defined in our generic interface with "RoomType". Likewise, for other controllers that wish to fetch data from the database using this interface will replace TEntity with their characteristic model class.

Reimplementing Our Controller Methods

Index Action
Delete the default code in the index method and replace it with the following:

  public async Task<IActionResult> Index()
        {
            return View(await _roomTypeService.GetAllItemsAsync());
        }
  • This action simply calls the GetAllItemsAsync() of our interface. Items now being RoomTypes.

Details Action

 public async Task<IActionResult> Details(Guid? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var roomType = await _roomTypeService.GetItemByIdAsync(id);

            if (roomType == null)
            {
                return NotFound();
            }
            return View(roomType);
        }
  • This method is also like the index action. It calls the GetItemByIdAsync() method of the interface, in which our GenericHotelService implementation calls the Find() method. This method returns an entity if the id provided matches any in the database. Otherwise it returns null.
    Cadfpture.PNG

Create Action
Note that our create action has two implementations - GET and POST which corresponds to the action that serves the form(view) and the one that processes the form submission request.

  • The GET overload simply returns the view:
public IActionResult Create()
        {
            return View();
        }
  • POST
[HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Create([Bind("Name,BasePrice,Description,ImageUrl")] RoomType roomType)
        {
            if (ModelState.IsValid)
            {
                roomType.ID = Guid.NewGuid();
                await _roomTypeService.CreateItemAsync(roomType);               
                return RedirectToAction(nameof(Index));
            }
            return View(roomType);
        }

Edit Action
The Edit action also has two overloads GET and POST just like the Create action.

  • GET
 public async Task<IActionResult> Edit(Guid? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var roomType = await _roomTypeService.GetItemByIdAsync(id);
            if (roomType == null)
            {
                return NotFound();
            }
            return View(roomType);
        }
  • POST
  [HttpPost]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit(Guid id, [Bind("ID,Name,BasePrice,Description,ImageUrl")] RoomType roomType)
        {
            if (id != roomType.ID)
            {
                return NotFound();
            }

            if (ModelState.IsValid)
            {
                try
                {
                    await _roomTypeService.EditItemAsync(roomType);
                }
                catch (DbUpdateConcurrencyException)
                {
                    if (_roomTypeService.GetItemByIdAsync(id) == null)
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
                return RedirectToAction(nameof(Index));
            }
            return View(roomType);
        }

Delete Action

  • GET
  public async Task<IActionResult> Delete(Guid? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            var roomType = await _roomTypeService.GetItemByIdAsync(id);                
            if (roomType == null)
            {
                return NotFound();
            }

            return View(roomType);
        }
  • POST
 [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> DeleteConfirmed(Guid id)
        {
            var roomType = await _roomTypeService.GetItemByIdAsync(id);
            await _roomTypeService.DeleteItemAsync(roomType);
            return RedirectToAction(nameof(Index));
        }      

CSRF and AntiForgery Token

As can be seen in the POST actions above, each of them are decorated with a [ValidateAntiForgeryToken]. This is a mechanism by which ASP.NET prevents Cross site Request Forgery(CSRF). CSRF is an attempt from a third party to post data to your site through a malicious form submitted on this third party site. It capitalizes on the fact that the user is already authenticated to your site, on their browser and it posts malicious scripts on behalf of the user, to your site.

ASP.NET prevents this by storing a value token as a session cookie on the user's browser. Each form to be submitted to our server must provide this token before the request is handled. By doing this, we ensure that every form submitted is a delibrate act of the logged in user.

To perform this in the previous .net framework version (ASP.NET MVC5), we would embed this token in a hidden field of our form by using the html tag helper.

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
     .
     .
     .
}

and thereafter, authenticate this on our controller action using the attribute

[ValidateAntiForgeryToken]
  • However, in ASP.NET core 2.0, simply using the asp-* tag helper would add the antiForgeryToken to your form.
 <form asp-action="Edit">

The hidden AntiForgeryToken input on our source code
Capturssse.PNG

Specifying the Service Implementation to Use

So far, we have injected the service interface into our controller and called methods of this interface. However, IGenericHotelService.cs is just an interface. The main code that does the actual database interaction is an implementation of the interface. We specify the implementation to be used in our Startup.cs class.
**Add this to the ConfigureServices method of your Startup.cs class

 public void ConfigureServices(IServiceCollection services)
        {
            .
            .
         
            services.AddScoped(typeof(IGenericHotelService<>), typeof(GenericHotelService<>));
        }

Results

Because we selected MVC Controllers with views when creating our controller, Our CRUD views were scaffolded for us. They aren't the sleekest of all though, but they are enough to present what we intend to display.
Also, since this tutorial series is not primarily dedicated to the view appearance(at least not now), we would leave it as it is right now.

  • Hit Ctrl F5 to build and run the project
  • Navigate to localhost:port/roomtypes/create)
  • Fill in the form to create a room type, and then view the index action to see the result
  • Play around with the details and delete actions to appreciate the changes

create.png

index.png

detials.png

delete.png

Curriculum

Proof of Work Done

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

Sort:  

You have a minor misspelling in the following sentence:

Remember in the previous tutorial, we talked about dependency injection and why it was neccessary to define an interface.
It should be necessary instead of neccessary.

Thank you for identifying that.

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:

  • In the images where you have code, put the most visible code. In your images it is difficult to see the code.
  • There are parts of the code that have little explanation, try to explain as much as possible.

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]

Thanks for your review and recommendations. Some parts of the code have little explanation because of the assumption that my audience already has some knowledge of the language syntax and consequently should be able to infer the function of those blocks. However, in my next tutorial, I will make sure to explain carefully every important detail.

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!