0

Basically, I have a small .NET MVC application which serves as a site to book hotel rooms.

Now, when creating a new hotel, in my Razor view I first have some fields needed to create a new hotel, obviously, and then a dynamically generated list of small and big beds to populate the new rooms of the hotel (see the code block at the end of the post), so that the user can click on a 'plus' button to have a new line with two input fields with the amount of small and big beds needed to create a new room.

Now, In my controller, this results in the following long and ugly method I thought of:

[HttpPost]
[ActionName("AddHotel")]
public ActionResult AddHotelPost()
{
     int[] amountOfBigBedsList = (Request.Form.GetValues("AmountOfBigBeds") ??
                                 throw new InvalidOperationException("The amount of beds for a room cannot be empty"))
                                 .Select(int.Parse).ToArray();

     int[] amountOfSmallBedsList = (Request.Form.GetValues("AmountOfSmallBeds") ??
                                 throw new InvalidOperationException("The amount of beds for a room cannot be empty"))
                                 .Select(int.Parse).ToArray();

     if (amountOfSmallBedsList.Length != amountOfBigBedsList.Length) return View("AddHotel");
// The amounts must match since they are both required

     Hotel newHotel = new Hotel
     {
           Name = Request.Form.Get("HotelName"),
           Location = new Location { City = Request.Form.Get("City"), Country = Request.Form.Get("Country") },
           Address = Request.Form.Get("Address"),
           Rooms = new List<HotelRoom>(),
     };

     for (int i = 0; i < amountOfSmallBedsList.Length; i++)
     {
           _uow.HotelRepository.AddRoom(newHotel, new HotelRoom
           {
                 AmountOfSmallBeds = amountOfSmallBedsList[i],
                 AmountOfBigBeds = amountOfBigBedsList[i],
                 HotelRefId = newHotel.Id,
                 Hotel = newHotel,
           });
     }

     _uow.HotelRepository.Add(newHotel);
     _uow.Save(); // Unit of Work

     return RedirectToAction("../Hotel/AllHotels", new { hotel_id = newHotel.Id });
}

I'd like to take advantage of Entity Framework binding to simply take, in the method signature, something like AddHotelPost(Hotel newHotel, List<HotelRoom> newHotelRooms) and for the framework to do all the work without all this long ugly code I wrote to manually get the form data and assign it to new Hotel and List objects.

BTW, the names used for the attributes in my models are the same as the 'name' fields I used in my html input tags.

I'm just started with .NET a few weeks ago, so be gentle haha. Any recommendations are greeted.

Razor view code:

<form method="post" action="/Hotel/AddHotel" class="form-inline my-2 my-lg-0 hotelForm">
    <div class="form-group">
        <input name="HotelName" class="form-control mr-sm-2" type="text" placeholder="AccommodationName" aria-label="AccommodationName" required>
        <input name="City" class="form-control mr-sm-2" type="text" placeholder="City" aria-label="City" required>
        <input name="Country" class="form-control mr-sm-2" type="text" placeholder="Country" aria-label="Country" required>
        <input name="Address" class="form-control mr-sm-2" type="text" placeholder="Address" aria-label="Address" required>
    </div>

    <br />
    <hr />

    <div class="form-group" id="addRoomAndSubmitSection">
        <button type="button" class="btn btn-default btn-sm" id="addRoomField"> <span class="glyphicon glyphicon-plus"></span></button>
        <button class="btn btn-outline-success my-2 my-sm-0" type="submit">Add Accommodation</button>
    </div>

    <script>document.getElementById('addRoomField').click();</script>
</form>

@section scripts
{
    <script>
        $(document).ready(function () {
            $("#addRoomField").click(function (event) {
                event.preventDefault();

                $(`<div class="form-group roomForm">
                    <input name="AmountOfBigBeds" class="form-control mr-sm-2" type="number" min="0" max="4" placeholder="Amount of big beds"
                    aria-label="Amount of big beds" style="width: 13em;" required>
                    <input name="AmountOfSmallBeds" class="form-control mr-sm-2" type="number" min="0" max="4" placeholder="Amount of small beds"
                    aria-label="Amount of small beds" style="width: 13em;" required>
                    </div>
                    <br />
                    <hr />`).insertBefore($(`#addRoomAndSubmitSection`));
            });
        });
    </script>
}
Roy
  • 27
  • 1
  • 6
  • Yeah, you do not need to do that. MVC has a great [model binder](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-2.2). What a lot of developers do is create a [view model](https://stackoverflow.com/questions/24469192/mvc-viewmodel-example) in their Get, build the view with [HtmlHelpers](https://www.c-sharpcorner.com/article/html-helpers-in-Asp-Net-mvc/) and then in the POST you can validate and then update the entities using a tool like AutoMapper. – Steve Greene Apr 04 '19 at 15:02

2 Answers2

0

In the controller, we have action AddHotelPost(), make it like AddHotelPost(FormCollection data), since you are posting the form. In data object, the whole information will be in string so convert it into other datatype as requirement like int[] rooms = Convert.Toint32(data["rooms"]);

For using EF, make a class named HotelContext (or as you like)

public class HotelContext:DbContext
    {
public HotelContext():base("type the **name** as you have in the connectionString")
        {
            this.Configuration.LazyLoadingEnabled = false;

        }
      public DbSet<Hotel> Hotels { get; set; }

Then in the ActionMethod, make a HotelContext object, then with that object call the Property Hotels and with that property use its Add method and pass the HotelObject to the Add(), also don't forget to save it, like this:

HotelContext context=new HotelContext();
context.Hotels.Add(Hotel);
context.SaveChanges();

I hope, it will help you.

0

First of all you need define the class properly. The Class

 public class Booking
{
    public int Id { get; set; }
    public DateTime Transdate { get; set; }
    public string HotelName { get; set; }
    public string HotelAddress { get; set; }

    public ICollection<BookingItem> BookingItems { get; set; }
}

And then the BookingItemClass

    public class BookingItem
{
    public int Id { get; set; }
    public int BookingId { get; set; }
    public int? Qty { get; set; }
    public RoomType RoomType { get; set; }

    public virtual Booking Booking { get; set; }
}

public enum RoomType { 
Small, Medium, Large, ExtraBed}

Then build a ViewModel for initiate the room type and also create the saving procedure

 public class BookingVM
{
    public Booking Booking { get; set; }
    public List<BookingItem> BookingItems { get; set; }
    public ApplicationDbContext db = new ApplicationDbContext();

    public void Initiate()
    {
        Booking = new Booking();
        BookingItems = new List<BookingItem>();

        // Get All Type of Room
        foreach (var item in Enum.GetValues(typeof(RoomType)))
        {
              BookingItem _item = new BookingItem
                {
                    RoomType = (RoomType)item,
                    Qty = 0,
                    BookingId = 0
                };

              BookingItems.Add(_item);
        }


    }

    public void AddBooking()
    {

        Booking.BookingItems = new List<BookingItem>();


        foreach (var item in BookingItems)
        {
            Booking.BookingItems.Add(item);
        }

        db.Bookings.Add(Booking);
    }
}

Then the controller will be super simple

public class HotelBookingController : Controller {

    public ActionResult Create() {
        BookingVM VM = new BookingVM();
        VM.Initiate();
        VM.Booking.Transdate = DateTime.Today.Date;

        return View(VM);
    }

    [HttpPost]
    public ActionResult Create(Booking Booking, List<BookingItem> BookingItems)
    {
        BookingVM VM = new BookingVM();
        VM.Booking = Booking;
        VM.BookingItems = BookingItems;

        VM.AddBooking();

        return View();
    }
}

The view will be something like this

@model HelpingRoy.ViewModel.BookingVM
@{
    ViewBag.Title = "Create";
}


@using (Html.BeginForm())
{

    <div class="form-group">
        @Html.LabelFor(a => a.Booking.Transdate)
        @Html.TextBoxFor(a => a.Booking.Transdate, new { @class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(a => a.Booking.HotelName)
        @Html.TextBoxFor(a => a.Booking.HotelName, new {@class = "form-control" })
    </div>

    <div class="form-group">
        @Html.LabelFor(a => a.Booking.HotelAddress)
        @Html.TextBoxFor(a => a.Booking.HotelAddress, new { @class = "form-control" })
    </div>

    <table class="table table-bordered table-striped">
        <thead>
           <tr>
               <th>Room Type</th>
               <th>Qty </th>
           </tr>
        </thead>
        @for (int i = 0; i < Model.BookingItems.Count(); i++)
        {
            
            @Html.HiddenFor(a => Model.BookingItems[i].RoomType)
            <tr>
                @*use display so that use cannot amend the room type*@
                <td>@Html.DisplayFor(a => Model.BookingItems[i].RoomType)</td>
                <td>@Html.TextBoxFor(a => Model.BookingItems[i].Qty)</td>
            </tr>
        }
    </table>
    
    <input  type="submit" value="SAVE" class="btn btn-success"/>
}

I tested it .. It should work and hope it helps!

Yat Fei Leong
  • 751
  • 1
  • 6
  • 10