Explore Developer Center's New Chatbot! MongoDB AI Chatbot can be accessed at the top of your navigation to answer all your MongoDB questions.

Introducing MongoDB 8.0, the fastest MongoDB ever!
MongoDB Developer
C#
plus
Sign in to follow topics
MongoDB Developer Centerchevron-right
Developer Topicschevron-right
Languageschevron-right
C#chevron-right

MongoDB Provider for EF Core Tutorial: Building an App with CRUD and Change Tracking

Luce Carter18 min read • Published Nov 21, 2023 • Updated Jan 24, 2024
.NETC#
Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Entity Framework (EF) has been part of .NET for a long time (since .NET 3.51) and is a popular object relational mapper (ORM) for many applications. EF has evolved into EF Core alongside the evolution of .NET. EF Core supports a number of different database providers and can now be used with MongoDB with the help of the MongoDB Provider for Entity Framework Core.
In this tutorial, we will look at how you can build a car booking application using the new MongoDB Provider for EF Core that will support create, read, update, and delete operations (CRUD) as well as change tracking, which helps to automatically update the database and only the fields that have changed.
A car booking system is a good example to explore the benefits of using EF Core with MongoDB because there is a need to represent a diverse range of entities. There will be entities like cars with their associated availability status and location, and bookings including the associated car.
As the system evolves and grows, ensuring data consistency can become challenging. Additionally, as users interact with the system, partial updates to data entities — like booking details or car specifications — will happen more and more frequently. Capturing and efficiently handling these updates is paramount for good system performance and data integrity.

Prerequisites

In order to follow along with this tutorial, you are going to need a few things:
If you just want to see example code, you can view the full code in the GitHub repository.

Create the project

ASP.NET Core is a very flexible web framework, allowing you to scaffold out different types of web applications that have slight differences in terms of their UI or structure. For this tutorial, we are going to create an MVC project that will make use of static files and controllers. There are other types of front end you could use, such as React, but MVC with .cshtml views is the most commonly used. To create the project, we are going to use the .NET CLI:
1dotnet new mvc -o SuperCarBookingSystem
Because we used the CLI, although easier, it only creates the csproj file and not the solution file which allows us to open it in Visual Studio, so we will fix that.
1cd SuperCarBookingSystem
2dotnet new sln
3dotnet sln .\SuperCarBookingSystem.sln add .\SuperCarBookingSystem.csproj

Add the NuGet packages

Now that we have the new project created, we will want to go ahead and add the required NuGet packages. Either using the NuGet Package Manager or using the .NET CLI commands below, add the MongoDB MongoDB.EntityFrameworkCore and Microsoft.EntityFrameworkCore packages.
1dotnet add package MongoDB.EntityFrameworkCore --version 7.0.0-preview.1
2dotnet add package Microsoft.EntityFrameworkCore
At the time of writing, the MongoDB.EntityFrameworkCore is in preview, so if using the NuGet Package Manager UI inside Visual Studio, be sure to tick the “include pre-release” box or you won’t get any results when searching for it.

Create the models

Before we can start implementing the new packages we just added, we need to create the models that represent the entities we want in our car booking system that will of course be stored in MongoDB Atlas as documents. In the following subsections, we will create the following models:
  • Car
  • Booking
  • MongoDBSettings

Car

First, we need to create our car model that will represent the cars that are available to be booked in our system.
  1. Create a new class in the Models folder called Car.
  2. Add the following code:
1using MongoDB.Bson;
2using MongoDB.EntityFrameworkCore;
3using System.ComponentModel.DataAnnotations;
4
5
6namespace SuperCarBookingSystem.Models
7{
8 [Collection("cars")]
9 public class Car
10 {
11
12 public ObjectId Id { get; set; }
13
14 [Required(ErrorMessage = "You must provide the make and model")]
15 [Display(Name = "Make and Model")]
16 public string? Model { get; set; }
17
18
19
20 [Required(ErrorMessage = "The number plate is required to identify the vehicle")]
21 [Display(Name = "Number Plate")]
22 public string NumberPlate { get; set; }
23
24
25 [Required(ErrorMessage = "You must add the location of the car")]
26 public string? Location { get; set; }
27
28
29 public bool IsBooked { get; set; } = false;
30 }
31}
The collection attribute before the class tells the application what collection inside the database we are using. This allows us to have differing names or capitalization between our class and our collection should we want to.

Booking

We also need to create a booking class to represent any bookings we take in our system.
  1. Create a new class inside the Models folder called Booking.
  2. Add the following code to it:
1 using MongoDB.Bson;
2using MongoDB.EntityFrameworkCore;
3using System.ComponentModel.DataAnnotations;
4
5
6namespace SuperCarBookingSystem.Models
7{
8 [Collection("bookings")]
9 public class Booking
10 {
11 public ObjectId Id { get; set; }
12
13
14 public ObjectId CarId { get; set; }
15
16
17 public string CarModel { get; set; }
18
19
20 [Required(ErrorMessage = "The start date is required to make this booking")]
21 [Display(Name = "Start Date")]
22 public DateTime StartDate { get; set; }
23
24
25 [Required(ErrorMessage = "The end date is required to make this booking")]
26 [Display(Name = "End Date")]
27 public DateTime EndDate { get; set; }
28 }
29}

MongoDBSettings

Although it won’t be a document in our database, we need a model class to store our MongoDB-related settings so they can be used across the application.
  1. Create another class in Models called MongoDBSettings.
  2. Add the following code:
1public class MongoDBSettings
2{
3 public string AtlasURI { get; set; }
4 public string DatabaseName { get; set; }
5}

Setting up EF Core

This is the exciting part. We are going to start to implement EF Core and take advantage of the new MongoDB Provider. If you are used to working with EF Core already, some of this will be familiar to you.

CarBookingDbContext

  1. In a location of your choice, create a class called CarBookingDbContext. I placed it inside a new folder called Services.
  2. Replace the code inside the namespace with the following:
1using Microsoft.EntityFrameworkCore;
2using SuperCarBookingSystem.Models;
3
4namespace SuperCarBookingSystem.Services
5{
6 public class CarBookingDbContext : DbContext
7 {
8 public DbSet<Car> Cars { get; init; }
9
10
11 public DbSet<Booking> Bookings { get; init; }
12
13
14 public CarBookingDbContext(DbContextOptions options)
15 : base(options)
16 {
17 }
18
19
20 protected override void OnModelCreating(ModelBuilder modelBuilder)
21 {
22 base.OnModelCreating(modelBuilder);
23
24
25 modelBuilder.Entity<Car>();
26 modelBuilder.Entity<Booking>();
27 }
28 }
29}
If you are used to EF Core, this will look familiar. The class extends the DbContext and we create DbSet properties that store the models that will also be present in the database. We also override the OnModelCreating method. You may notice that unlike when using SQL Server, we don’t call .ToTable(). We could call ToCollection instead but this isn’t required here as we specify the collection using attributes on the classes.

Add connection string and database details to appsettings

Earlier, we created a MongoDBSettings model, and now we need to add the values that the properties map to into our appsettings.
  1. In both appsettings.json and appsettings.Development.json, add the following new section:
    1 "MongoDBSettings": {
    2 "AtlasURI": "mongodb+srv://<username>:<password>@<url>",
    3 "DatabaseName": "cargarage"
    4 }
  2. Replace the Atlas URI with your own connection string from Atlas.

Updating program.cs

Now we have configured our models and DbContext, it is time to add them to our program.cs file.
After the existing line builder.Services.AddControllersWithViews();, add the following code:
1var mongoDBSettings = builder.Configuration.GetSection("MongoDBSettings").Get<MongoDBSettings>();
2builder.Services.Configure<MongoDBSettings>(builder.Configuration.GetSection("MongoDBSettings"));
3
4builder.Services.AddDbContext<CarBookingDbContext>(options =>
5options.UseMongoDB(mongoDBSettings.AtlasURI ?? "", mongoDBSettings.DatabaseName ?? ""));

Creating the services

Now, it is time to add the services we will use to talk to the database via the CarBookingDbContext we created. For each service, we will create an interface and the class that implements it.

ICarService and CarService

The first interface and service we will implement is for carrying out the CRUD operations on the cars collection. This is known as the repository pattern. You may see people interact with the DbContext directly. But most people use this pattern, which is why we are including it here.
  1. If you haven’t already, create a Services folder to store our new classes.
  2. Create an ICarService interface and add the following code for the methods we will implement:
    1using MongoDB.Bson;
    2using SuperCarBookingSystem.Models;
    3
    4namespace SuperCarBookingSystem.Services
    5{
    6 public interface ICarService
    7 {
    8 IEnumerable<Car> GetAllCars();
    9 Car? GetCarById(ObjectId id);
    10
    11 void AddCar(Car newCar);
    12
    13 void EditCar(Car updatedCar);
    14
    15 void DeleteCar(Car carToDelete);
    16 }
    17}
  3. Create a CarService class file.
  4. Update the CarService class declaration so it implements the ICarService we just created:
    1using Microsoft.EntityFrameworkCore;
    2using MongoDB.Bson;
    3using MongoDB.Driver;
    4using SuperCarBookingSystem.Models;
    5
    6namespace SuperCarBookingSystem.Services
    7{
    8 public class CarService : ICarService
    9 {
  5. This will cause a red squiggle to appear underneath ICarService as we haven’t implemented all the methods yet, but we will implement the methods one by one.
  6. Add the following code after the class declaration that adds a local CarBookingDbContext object and a constructor that gets an instance of the DbContext via dependency injection.
    1 private readonly CarBookingDbContext _carDbContext;
    2 public CarService(CarBookingDbContext carDbContext)
    3 {
    4 _carDbContext = carDbContext;
    5 }
  7. Next, we will implement the GetAllCars method so add the following code:
    1public IEnumerable<Car> GetAllCars()
    2{
    3 return _carDbContext.Cars.OrderBy(c => c.Id).AsNoTracking().AsEnumerable<Car>();
    4}
    The id property here maps to the _id field in our document which is a special MongoDB ObjectId type and is auto-generated when a new document is created. But what is useful about the _id property is that it can actually be used to order documents because of how it is generated under the hood.
    If you haven’t seen it before, the AsNoTracking() method is part of EF Core and prevents EF tracking changes you make to an object. This is useful for reads when you know no changes are going to occur.
  8. Next, we will implement the method to get a specific car using its Id property:
    1public Car? GetCarById(ObjectId id)
    2{
    3 return _carDbContext.Cars.FirstOrDefault(c => c.Id == id);
    4}
    Then, we will add the AddCar implementation:
    1public void AddCar(Car car)
    2{
    3 _carDbContext.Cars.Add(car);
    4
    5 _carDbContext.ChangeTracker.DetectChanges();
    6 Console.WriteLine(_carDbContext.ChangeTracker.DebugView.LongView);
    7
    8 _carDbContext.SaveChanges();
    9}
    In a production environment, you might want to use something like ILogger to track these changes rather than printing to the console. But this will allow us to clearly see that a new entity has been added, showing change tracking in action.
  9. EditCar is next:
    1public void EditCar(Car car)
    2{
    3 var carToUpdate = _carDbContext.Cars.FirstOrDefault(c => c.Id == car.Id);
    4
    5 if(carToUpdate != null)
    6 {
    7 carToUpdate.Model = car.Model;
    8 carToUpdate.NumberPlate = car.NumberPlate;
    9 carToUpdate.Location = car.Location;
    10 carToUpdate.IsBooked = car.IsBooked;
    11
    12 _carDbContext.Cars.Update(carToUpdate);
    13
    14 _carDbContext.ChangeTracker.DetectChanges();
    15 Console.WriteLine(_carDbContext.ChangeTracker.DebugView.LongView);
    16
    17 _carDbContext.SaveChanges();
    18
    19 }
    20 else
    21 {
    22 throw new ArgumentException("The car to update cannot be found. ");
    23 }
    24}
    Again, we add a call to print out information from change tracking as it will show that the new EF Core Provider, even when using MongoDB as the database, is able to track modifications.
  10. Finally, we need to implement DeleteCar:
    1public void DeleteCar(Car car)
    2{
    3 var carToDelete = _carDbContext.Cars.Where(c => c.Id == car.Id).FirstOrDefault();
    4
    5 if(carToDelete != null) {
    6 _carDbContext.Cars.Remove(carToDelete);
    7 _carDbContext.ChangeTracker.DetectChanges();
    8 Console.WriteLine(_carDbContext.ChangeTracker.DebugView.LongView);
    9 _carDbContext.SaveChanges();
    10 }
    11 else {
    12 throw new ArgumentException("The car to delete cannot be found.");
    13 }
    14}

IBookingService and BookingService

Next up is our IBookingService and BookingService.
  1. Create the IBookingService interface and add the following methods:
    1using MongoDB.Bson;
    2using SuperCarBookingSystem.Models;
    3namespace SuperCarBookingSystem.Services
    4{
    5 public interface IBookingService
    6 {
    7 IEnumerable<Booking> GetAllBookings();
    8 Booking? GetBookingById(ObjectId id);
    9
    10 void AddBooking(Booking newBooking);
    11
    12 void EditBooking(Booking updatedBooking);
    13
    14 void DeleteBooking(Booking bookingToDelete);
    15 }
    16}
  2. Create the BookingService class, and replace your class with the following code that implements all the methods:
    1using Microsoft.EntityFrameworkCore;
    2using MongoDB.Bson;
    3using SuperCarBookingSystem.Models;
    4
    5namespace SuperCarBookingSystem.Services
    6{
    7 public class BookingService : IBookingService
    8 {
    9 private readonly CarBookingDbContext _carDbContext;
    10
    11 public BookingService(CarBookingDbContext carDBContext)
    12 {
    13 _carDbContext = carDBContext;
    14 }
    15 public void AddBooking(Booking newBooking)
    16 {
    17 var bookedCar = _carDbContext.Cars.FirstOrDefault(c => c.Id == newBooking.CarId);
    18 if (bookedCar == null)
    19 {
    20 throw new ArgumentException("The car to be booked cannot be found.");
    21 }
    22
    23 newBooking.CarModel = bookedCar.Model;
    24
    25 bookedCar.IsBooked = true;
    26 _carDbContext.Cars.Update(bookedCar);
    27
    28 _carDbContext.Bookings.Add(newBooking);
    29
    30 _carDbContext.ChangeTracker.DetectChanges();
    31 Console.WriteLine(_carDbContext.ChangeTracker.DebugView.LongView);
    32
    33 _carDbContext.SaveChanges();
    34 }
    35
    36 public void DeleteBooking(Booking booking)
    37 {
    38 var bookedCar = _carDbContext.Cars.FirstOrDefault(c => c.Id == booking.CarId);
    39 bookedCar.IsBooked = false;
    40
    41 var bookingToDelete = _carDbContext.Bookings.FirstOrDefault(b => b.Id == booking.Id);
    42
    43 if(bookingToDelete != null)
    44 {
    45 _carDbContext.Bookings.Remove(bookingToDelete);
    46 _carDbContext.Cars.Update(bookedCar);
    47
    48 _carDbContext.ChangeTracker.DetectChanges();
    49 Console.WriteLine(_carDbContext.ChangeTracker.DebugView.LongView);
    50
    51 _carDbContext.SaveChanges();
    52 }
    53 else
    54 {
    55 throw new ArgumentException("The booking to delete cannot be found.");
    56 }
    57 }
    58
    59 public void EditBooking(Booking updatedBooking)
    60 {
    61 var bookingToUpdate = _carDbContext.Bookings.FirstOrDefault(b => b.Id == updatedBooking.Id);
    62
    63
    64 if (bookingToUpdate != null)
    65 {
    66 bookingToUpdate.StartDate = updatedBooking.StartDate;
    67 bookingToUpdate.EndDate = updatedBooking.EndDate;
    68
    69
    70 _carDbContext.Bookings.Update(bookingToUpdate);
    71
    72 _carDbContext.ChangeTracker.DetectChanges();
    73 _carDbContext.SaveChanges();
    74
    75 Console.WriteLine(_carDbContext.ChangeTracker.DebugView.LongView);
    76 }
    77 else
    78 {
    79 throw new ArgumentException("Booking to be updated cannot be found");
    80 }
    81
    82 }
    83
    84 public IEnumerable<Booking> GetAllBookings()
    85 {
    86 return _carDbContext.Bookings.OrderBy(b => b.StartDate).AsNoTracking().AsEnumerable<Booking>();
    87 }
    88
    89 public Booking? GetBookingById(ObjectId id)
    90 {
    91 return _carDbContext.Bookings.AsNoTracking().FirstOrDefault(b => b.Id == id);
    92 }
    93
    94 }
    95}
This code is very similar to the code for the CarService class but for bookings instead.

Adding them to Dependency Injection

The final step for the services is to add them to the dependency injection container.
Inside Program.cs, add the following code after the code we added there earlier:
1builder.Services.AddScoped<ICarService, CarService>();
2builder.Services.AddScoped<IBookingService, BookingService>();

Creating the view models

Before we implement the front end, we need to add the view models that will act as a messenger between our front and back ends where required. Even though our application is quite simple, implementing the view model is still good practice as it helps decouple the pieces of the app.

CarListViewModel

The first one we will add is the CarListViewModel. This will be used as the model in our Razor page later on for listing cars in our database.
  1. Create a new folder in the root of the project called ViewModels.
  2. Add a new class called CarListViewModel.
  3. Add public IEnumerable<Car> Cars { get; set; } inside your class.

CarAddViewModel

We also want a view model that can be used by the Add view we will add later.
  1. Inside the ViewModels folder, create a new class called CarAddViewModel.
  2. Add public Car? Car { get; set; }.

BookingListViewModel

Now, we want to do something very similar for bookings, starting with BookingListViewModel.
  1. Create a new class in the ViewModels folder called BookingListViewModel.
  2. Add public IEnumerable<Booking> Bookings { get; set; }.

BookingAddViewModel

Finally, we have our BookingAddViewModel.
Create the class and add the property public Booking? Booking { get; set; } inside the class.

Adding to _ViewImports

Later on, we will be adding references to our models and viewmodels in the views. In order for the application to know what they are, we need to add references to them in the _ViewImports.cshtml file inside the Views folder.
There will already be some references in there, including TagHelpers, so we want to add references to our .Models and .ViewModels folders. When added, it will look something like below, just with your application name instead.
1@using <YourApplicationName>
2@using <YourApplicationName>.Models
3@using <YourApplicationName>.ViewModels

Creating the controllers

Now we have the backend implementation and the view models we will refer to, we can start working toward the front end. We will be creating two controllers: one for Car and one for Booking.

CarController

The first controller we will add is for the car.
  1. Inside the existing Controllers folder, add a new controller. If using Visual Studio, use the MVC Controller - Empty controller template.
  2. Add a local ICarService object and a constructor that fetches it from dependency injection:
    1private readonly ICarService _carService;
    2
    3
    4public CarController(ICarService carService)
    5{
    6 _carService = carService;
    7}
  3. Depending on what your scaffolded controller came with, either create or update the Index function with the following:
    1public IActionResult Index()
    2{
    3 CarListViewModel viewModel = new()
    4 {
    5 Cars = _carService.GetAllCars(),
    6 };
    7 return View(viewModel);
    8}
    For the other CRUD operations — so create, update, and delete — we will have two methods for each: one is for Get and the other is for Post.
  4. The HttpGet for Add will be very simple as it doesn’t need to pass any data around:
    1public IActionResult Add()
    2{
    3 return View();
    4}
  5. Next, add the Add method that will be called when a new car is requested to be added:
    1 [HttpPost]
    2 public IActionResult Add(CarAddViewModel carAddViewModel)
    3 {
    4 if(ModelState.IsValid)
    5 {
    6 Car newCar = new()
    7 {
    8 Model = carAddViewModel.Car.Model,
    9 Location = carAddViewModel.Car.Location,
    10 NumberPlate = carAddViewModel.Car.NumberPlate
    11 };
    12
    13
    14 _carService.AddCar(newCar);
    15 return RedirectToAction("Index");
    16 }
    17
    18
    19 return View(carAddViewModel);
    20 }
  6. Now, we will add the code for editing a car:
    1 public IActionResult Edit(string id)
    2 {
    3 if(id == null)
    4 {
    5 return NotFound();
    6 }
    7
    8
    9 var selectedCar = _carService.GetCarById(new ObjectId(id));
    10 return View(selectedCar);
    11 }
    12
    13
    14 [HttpPost]
    15 public IActionResult Edit(Car car)
    16 {
    17 try
    18 {
    19 if(ModelState.IsValid)
    20 {
    21 _carService.EditCar(car);
    22 return RedirectToAction("Index");
    23 }
    24 else
    25 {
    26 return BadRequest();
    27 }
    28 }
    29 catch (Exception ex)
    30 {
    31 ModelState.AddModelError("", $"Updating the car failed, please try again! Error: {ex.Message}");
    32 }
    33
    34
    35 return View(car);
    36 }
  7. Finally, we have Delete:
    1public IActionResult Delete(string id) {
    2 if (id == null)
    3 {
    4 return NotFound();
    5 }
    6
    7
    8 var selectedCar = _carService.GetCarById(new ObjectId(id));
    9 return View(selectedCar);
    10}
    11
    12
    13[HttpPost]
    14public IActionResult Delete(Car car)
    15{
    16 if (car.Id == null)
    17 {
    18 ViewData["ErrorMessage"] = "Deleting the car failed, invalid ID!";
    19 return View();
    20 }
    21
    22
    23 try
    24 {
    25 _carService.DeleteCar(car);
    26 TempData["CarDeleted"] = "Car deleted successfully!";
    27
    28
    29 return RedirectToAction("Index");
    30 }
    31 catch (Exception ex)
    32 {
    33 ViewData["ErrorMessage"] = $"Deleting the car failed, please try again! Error: {ex.Message}";
    34 }
    35
    36
    37 var selectedCar = _carService.GetCarById(car.Id);
    38 return View(selectedCar);
    39}

BookingController

Now for the booking controller. This is very similar to the CarController but it has a reference to both the car and booking service as we need to associate a car with a booking. This is because at the moment, the EF Core Provider doesn’t support relationships between entities so we can relate entities in a different way. You can view the roadmap on the GitHub repo, however.
  1. Create another empty MVC Controller called BookingController.
  2. Paste the following code replacing the current class:
1 public class BookingController : Controller
2 {
3 private readonly IBookingService _bookingService;
4 private readonly ICarService _carService;
5
6 public BookingController(IBookingService bookingService, ICarService carService)
7 {
8 _bookingService = bookingService;
9 _carService = carService;
10 }
11
12 public IActionResult Index()
13 {
14 BookingListViewModel viewModel = new BookingListViewModel()
15 {
16 Bookings = _bookingService.GetAllBookings()
17 };
18 return View(viewModel);
19 }
20
21 public IActionResult Add(string carId)
22 {
23 var selectedCar = _carService.GetCarById(new ObjectId(carId));
24
25 BookingAddViewModel bookingAddViewModel = new BookingAddViewModel();
26
27 bookingAddViewModel.Booking = new Booking();
28 bookingAddViewModel.Booking.CarId = selectedCar.Id;
29 bookingAddViewModel.Booking.CarModel = selectedCar.Model;
30 bookingAddViewModel.Booking.StartDate = DateTime.UtcNow;
31 bookingAddViewModel.Booking.EndDate = DateTime.UtcNow.AddDays(1);
32
33 return View(bookingAddViewModel);
34 }
35
36 [HttpPost]
37 public IActionResult Add(BookingAddViewModel bookingAddViewModel)
38 {
39 Booking newBooking = new()
40 {
41 CarId = bookingAddViewModel.Booking.CarId,
42 StartDate = bookingAddViewModel.Booking.StartDate,
43 EndDate = bookingAddViewModel.Booking.EndDate,
44 };
45
46 _bookingService.AddBooking(newBooking);
47 return RedirectToAction("Index");
48 }
49
50 public IActionResult Edit(string Id)
51 {
52 if(Id == null)
53 {
54 return NotFound();
55 }
56
57 var selectedBooking = _bookingService.GetBookingById(new ObjectId(Id));
58 return View(selectedBooking);
59 }
60
61 [HttpPost]
62 public IActionResult Edit(Booking booking)
63 {
64 try
65 {
66 var existingBooking = _bookingService.GetBookingById(booking.Id);
67 if (existingBooking != null)
68 {
69 _bookingService.EditBooking(existingBooking);
70 return RedirectToAction("Index");
71 }
72 else
73 {
74 ModelState.AddModelError("", $"Booking with ID {booking.Id} does not exist!");
75 }
76 }
77 catch (Exception ex)
78 {
79 ModelState.AddModelError("", $"Updating the booking failed, please try again! Error: {ex.Message}");
80 }
81
82 return View(booking);
83 }
84
85 public IActionResult Delete(string Id)
86 {
87 if (Id == null)
88 {
89 return NotFound();
90 }
91
92 var selectedBooking = _bookingService.GetBookingById(Id);
93 return View(selectedBooking);
94 }
95
96 [HttpPost]
97 public IActionResult Delete(Booking booking)
98 {
99 if(booking.Id == null)
100 {
101 ViewData["ErrorMessage"] = "Deleting the booking failed, invalid ID!";
102 return View();
103 }
104
105 try
106 {
107 _bookingService.DeleteBooking(booking);
108 TempData["BookingDeleted"] = "Booking deleted successfully";
109
110 return RedirectToAction("Index");
111 }
112 catch (Exception ex)
113 {
114 ViewData["ErrorMessage"] = $"Deleting the booking failed, please try again! Error: {ex.Message}";
115 }
116
117 var selectedCar = _bookingService.GetBookingById(booking.Id.ToString());
118 return View(selectedCar);
119 }
120 }

Creating the views

Now we have the back end and the controllers prepped with the endpoints for our car booking system, it is time to implement the views. This will be using Razor pages. You will also see reference to classes from Bootstrap as this is the CSS framework that comes with MVC applications out of the box. We will be providing views for the CRUD operations for both listings and bookings.

Listing Cars

First, we will provide a view that will map to the root of /Car, which will by convention look at the Index method we implemented.
ASP.NET Core MVC uses a convention pattern whereby you name the .cshtml file the name of the endpoint/method it uses and it lives inside a folder named after its controller.
  1. Inside the Views folder, create a new subfolder called Car.
  2. Inside that Car folder, add a new view. If using the available templates, you want Razor View - Empty. Name the view Index.
  3. Delete the contents of the file and add a reference to the CarListViewModel at the top @model CarListViewModel.
  4. Next, we want to add a placeholder for the error handling. If there was an issue deleting a car, we added a string to TempData so we want to add that into the view, if there is data to display.
    1@if (TempData["CarDeleted"] != null)
    2{
    3 <p class="text-success">@TempData["CarDeleted"]</p>
    4}
  5. Next, we will handle if there are no cars in the database, by displaying a message to the user:
    1@if (!Model.Cars.Any())
    2{
    3 <p>No results</p>
    4}
  6. The easiest way to display the list of cars and the relevant information is to use a table:
    1else
    2{
    3 <table class="table table-condensed table-bordered">
    4 <tr>
    5 <th>
    6 Model
    7 </th>
    8 <th>
    9 Number Plate
    10 </th>
    11 <th>
    12 Location
    13 </th>
    14 <th>
    15 Actions
    16 </th>
    17 </tr>
    18
    19 @foreach (var car in Model.Cars)
    20 {
    21 <tr>
    22 <td>@car.Model</td>
    23 <td>@car.NumberPlate</td>
    24 <td>@car.Location</td>
    25 <td>
    26 <a asp-action="Edit" asp-route-id="@car.Id.ToString()">Edit</a>
    27 <a asp-action="Delete" asp-route-id="@car.Id.ToString()">Delete</a>
    28 @if(!car.IsBooked)
    29 {
    30 <a asp-controller="Booking" asp-action="Add" asp-route-carId="@car.Id.ToString()">Book</a>
    31 }
    32 </td>
    33 </tr>
    34 }
    35
    36 </table>
    37}
    38
    39<p>
    40 <a class="btn btn-primary" asp-action="Add">Add new car</a>
    41</p>
    It makes sense to have the list of cars as our home page so before we move on, we will update the default route from Home to /Car.
  7. In Program.cs, inside app.MapControllerRoute, replace the pattern line with the following:
    1pattern: "{controller=Car}/{action=Index}/{id?}");
If we ran this now, the buttons would lead to 404s because we haven’t implemented them yet. So let’s do that now.

Adding cars

We will start with the form for adding new cars.
  1. Add a new, empty Razor View inside the Car subfolder called Add.cshtml.
  2. Before adding the form, we will add the model reference at the top, a header, and some conditional content for the error message.
    1@model CarAddViewModel
    2
    3
    4<h2>Create a new car</h2>
    5<hr />
    6
    7
    8@if (ViewData["ErrorMessage"] != null)
    9{
    10 <p class="text-danger">@ViewData["ErrorMessage"]</p>
    11}
  3. Now, we can implement the form.
    1<form method="post" asp-controller="Car" asp-action="Add">
    2 <div asp-validation-summary="All" class="text-danger"></div>
    3
    4
    5 <div class="mb-3">
    6 <label asp-for="Car.Model" class="form-label"></label>
    7 <input asp-for="Car.Model" class="form-control" />
    8 <span asp-validation-for="Car.Model" class="text-danger"></span>
    9 </div>
    10
    11
    12 <div class="mb-3">
    13 <label asp-for="Car.NumberPlate" class="form-label"></label>
    14 <input asp-for="Car.NumberPlate" class="form-control" />
    15 <span asp-validation-for="Car.NumberPlate" class="text-danger"></span>
    16 </div>
    17
    18
    19 <div class="mb-3">
    20 <label asp-for="Car.Location" class="form-label"></label>
    21 <input asp-for="Car.Location" class="form-control" />
    22 <span asp-validation-for="Car.Location" class="text-danger"></span>
    23 </div>
    24
    25
    26 <input type="submit" value="Add car" class="btn btn-primary" />
    27</form>
Now, we want to add a button at the bottom to easily navigate back to the list of cars in case the user decides not to add a new car after all. Add the following after the </form> tag:
1<div>
2 <a asp-controller="Car" asp-action="Index">Back to list</a>
3</div>

Editing cars

The code for the Edit page is almost identical to Add, but it uses the Car as a model as it will use the car it is passed to pre-populate the form for editing.
  1. Add another view inside the Car subfolder called Edit.cshtml.
  2. Add the following code:
    1@model Car
    2
    3<h2>Update @Model.Model</h2>
    4<hr />
    5
    6<form method="post" asp-controller="Car" asp-action="Edit">
    7 <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    8 <input type="hidden" asp-for="Id" />
    9
    10 <div class="mb-3">
    11 <label asp-for="Model" class="form-label"></label>
    12 <input asp-for="Model" class="form-control" />
    13 <span asp-validation-for="Model" class="text-danger"/>
    14 </div>
    15 <div class="mb-3">
    16 <label asp-for="NumberPlate" class="form-label"></label>
    17 <input asp-for="NumberPlate" class="form-control" />
    18 <span asp-validation-for="NumberPlate" class="text-danger"/>
    19 </div>
    20 <div class="mb-3">
    21 <label asp-for="Location" class="form-label"></label>
    22 <input asp-for="Location" class="form-control" />
    23 <span asp-validation-for="Location" class="text-danger"/>
    24 </div>
    25 <input type="submit" value="Update car" class="btn btn-primary" />
    26</form>
    27<div>
    28 <a asp-controller="Car" asp-action="Index">Back to list</a>
    29</div>

Deleting cars

The final page we need to implement is the page that is called when the delete button is clicked for a car.
  1. Create a new empty View called Delete.cshtml.
  2. Add the following code to add the model, heading, and conditional error message:
    1@model Car
    2
    3
    4<h2>Deleting @Model.Model</h2>
    5<hr />
    6
    7
    8@if(ViewData["ErrorMessage"] != null)
    9{
    10 <p class="text-danger">@ViewData["ErrorMessage"]</p>
    11}
    Instead of a form like in the other views, we are going to add a description list to display information about the car that we are confirming deletion of.
    1<div>
    2 <dl class="row">
    3 <dt class="col-sm-2">
    4 <label asp-for="Model"></label>
    5 </dt>
    6 <dd class="col-sm-10">
    7 @Model?.Model
    8 </dd>
    9 <dt class="col-sm-2">
    10 <label asp-for="NumberPlate"></label>
    11 </dt>
    12 <dd class="col-sm-10">
    13 @Model?.NumberPlate
    14 </dd>
    15 <dt class="col-sm-2">
    16 <label asp-for="Location"></label>
    17 </dt>
    18 <dd class="col-sm-10">
    19 @Model?.Location
    20 </dd>
    21
    22
    23 </dl>
    24</div>


  3. Below that, we will add a form for submitting the deletion and the button to return to the list:

    1<form method="post" asp-action="Delete">
    2 <input type="hidden" asp-for="Id" />
    3 <input type="submit" value="Delete car" class="btn btn-danger" onclick="javascript: return confirm('Are you sure you want to delete this car?');" />
    4</form>
    5
    6
    7<div>
    8 <a asp-controller="Car" asp-action="Index">Back to list</a>
    9</div>

Listing bookings

We have added the views for the cars so now we will add the views for bookings, starting with listing any existing books.
  1. Create a new folder inside the Views folder called Booking.
  2. Create a new empty view called Index.
  3. Add the following code to display the bookings, if any exist:
    1@model BookingListViewModel
    2
    3
    4@if (TempData["BookingDeleted"] != null)
    5{
    6 <p class="text-success">@TempData["BookingDeleted"]</p>
    7}
    8
    9
    10@if (!Model.Bookings.Any())
    11{
    12 <p>No results</p>
    13}
    14
    15
    16else
    17{
    18 <table class="table table-condensed table-bordered">
    19 <tr>
    20 <th>
    21 Booked Car
    22 </th>
    23 <th>
    24 Start Date
    25 </th>
    26 <th>
    27 End Date
    28 </th>
    29 <th>
    30 Actions
    31 </th>
    32 </tr>
    33
    34
    35 @foreach(var booking in Model.Bookings)
    36 {
    37 <tr>
    38 <td>@booking.CarModel</td>
    39 <td>@booking.StartDate</td>
    40 <td>@booking.EndDate</td>
    41 <td>
    42 <a asp-action="Edit" asp-route-id="@booking.Id.ToString()">Edit</a>
    43 <a asp-action="Delete" asp-route-id="@booking.Id.ToString()">Delete</a>
    44 </td>
    45 </tr>
    46 }
    47
    48
    49 </table>
    50
    51
    52}

Adding bookings

Adding bookings is next. This view will be available when the book button is clicked next to a listed car.
  1. Create an empty view called Add.cshtml.
  2. Add the following code:
    1@model BookingAddViewModel
    2
    3
    4@if (ViewData["ErrorMessage"] != null)
    5{
    6 <p class="text-danger">@ViewData["ErrorMessage"]</p>
    7}
    8
    9<form method="post" asp-controller="Booking" asp-action="Add">
    10 <div asp-validation-summary="All" class="text-danger"></div>
    11 <input type="hidden" asp-for="Booking.Id" />
    12 <input type="hidden" asp-for="Booking.CarId" />
    13
    14 <div class="mb-3">
    15 <label asp-for="Booking.StartDate" class="form-label"></label>
    16 <input asp-for="Booking.StartDate" type="date" class="form-control" />
    17 <span asp-validation-for="Booking.StartDate" class="text-danger"></span>
    18 </div>
    19 <div class="mb-3">
    20 <label asp-for="Booking.EndDate" class="form-label"></label>
    21 <input asp-for="Booking.EndDate" type="date" class="form-control" />
    22 <span asp-validation-for="Booking.EndDate" class="text-danger"></span>
    23 </div>
    24
    25 <input type="submit" value="Book car" class="btn btn-primary" />
    26</form>

Editing bookings

Just like with cars, we also want to be able to edit existing books.
  1. Create an empty view called Edit.cshtml.
  2. Add the following code:
    1@model Booking
    2
    3
    4<h2>Editing booking for @Model.CarModel between @Model.StartDate and @Model.EndDate</h2>
    5<hr />
    6
    7
    8<form method="post" asp-controller="Booking" asp-action="Edit">
    9 <div asp-validation-summary="ModelOnly" class="text-danger"></div>
    10 <input type="hidden" asp-for="Id" />
    11
    12
    13 <div class="mb-3">
    14 <label asp-for="StartDate" class="form-label"></label>
    15 <input asp-for="StartDate" class="form-control" />
    16 <span asp-validation-for="StartDate" class="text-danger" />
    17 </div>
    18 <div class="mb-3">
    19 <label asp-for="EndDate" class="form-label"></label>
    20 <input asp-for="EndDate" class="form-control" />
    21 <span asp-validation-for="EndDate" class="text-danger" />
    22 </div>
    23 <input type="submit" value="Update booking" class="btn btn-primary" />
    24</form>
    25<div>
    26 <a asp-controller="Booking" asp-action="Index">Back to bookings</a>
    27</div>

Deleting bookings

The final view we need to add is to delete a booking. As with cars, we will display the booking information and deletion confirmation.
1@model Booking
2
3<h2>Delete booking</h2>
4<hr />
5
6@if (ViewData["ErrorMessage"] != null)
7{
8 <p class="text-danger">@ViewData["ErrorMessage"]</p>
9}
10
11<div>
12 <dl class="row">
13 <dt class="col-sm-2">
14 <label asp-for="CarModel"></label>
15 </dt>
16 <dd class="col-sm-10">
17 @Model?.CarModel
18 </dd>
19 <dt class="col-sm-2">
20 <label asp-for="StartDate"></label>
21 </dt>
22 <dd class="col-sm-10">
23 @Model?.StartDate
24 </dd>
25 </dl>
26 <dt class="col-sm-2">
27 <label asp-for="EndDate"></label>
28 </dt>
29 <dd class="col-sm-10">
30 @Model?.EndDate
31 </dd>
32</div>
33
34<form method="post" asp-action="Delete">
35 <input type="hidden" asp-for="Id" />
36 <input type="hidden" asp-for="CarId" />
37 <input type="submit" value="Delete booking" class="btn btn-danger" onclick="javascript: return confirm('Are you sure you want to delete this booking?');" />
38</form>
39
40<div>
41 <a asp-controller="Booking" asp-action="Index">Back to list</a>
42</div>
If you want to view the full solution code, you can find it in the GitHub Repo.

Testing our application

We now have a functioning application that uses the new MongoDB Provider for EF Core — hooray! Now is the time to test it all and visit our endpoints to make sure it all works.
It is not part of this tutorial as it is not required, but I chose to make some changes to the site.css file to add some color. I also updated the _Layout.cshtml file to add the Car and Bookings pages to the navbar. You will see this reflected in the screenshots in the rest of the article. You are of course welcome to make your own changes if you have ideas of how you would like the application to look.

Cars

Below are some screenshots I took from the app, showing the features of the Cars endpoint.
List of cars in the garage with their details and action buttons to edit, delete and book
Form for adding a new car
Form for updating a specific vehicle
Deletion confirmation page showing details of car to be deleted

Bookings

The bookings pages will look very similar to cars but are adapted for the bookings model that includes dates.
List of bookings and actions to taken against them
Editing form for an existing booking

Conclusion

There we have it: a full stack application using ASP.NET MVC that takes advantage of the new MongoDB Provider for EF Core. We are able to do the CRUD operations and track changes. EF Core is widely used amongst developers so having an official MongoDB Provider is super exciting. This library is in Preview, which means we are continuing to build out new features. Stay tuned for updates and we are always open to feedback. We can’t wait to see what you build!
You can view the Roadmap of the provider in the GitHub repository, where you can also find links to the documentation!
As always, if you have any questions about this or other topics, get involved at our MongoDB Community Forums.

Facebook Icontwitter iconlinkedin icon
Rate this tutorial
star-empty
star-empty
star-empty
star-empty
star-empty
Related
Tutorial

Using Polymorphism with MongoDB and C#


Apr 30, 2024 | 6 min read
Tutorial

Getting Started with MongoDB Atlas and Azure Functions using .NET and C#


Apr 02, 2024 | 8 min read
Tutorial

Integrate Azure Key Vault with MongoDB Client-Side Field Level Encryption


May 24, 2022 | 9 min read
Tutorial

Saving Data in Unity3D Using PlayerPrefs


Sep 09, 2024 | 11 min read
Table of Contents