Tutorial do provedor MongoDB para o EF Core: criação de um aplicativo com CRUD e controle de alterações
Avalie esse Tutorial
O Entity Framework (EF) faz parte do .NET há muito tempo (desde o .NET 3.51) e é um mapeador relacional de objetos (ORM) popular para muitos aplicativos. O EF desenvolveu-se para o EF Core juntamente com a evolução do .NET. O EF Core oferece suporte a vários provedores de banco de dados diferentes e agora pode ser usado com o MongoDB com a ajuda do provedor MongoDB para Entity Framework Core.
Neste tutorial, vamos ver como você pode criar um aplicativo de reserva de carro usando o novo MongoDB Provider para o EF Core, que oferece suporte às operações de criação, leitura, atualização e exclusão (CRUD), bem como ao rastreamento de alterações, que ajuda a atualizar automaticamente o banco de dados e somente os campos que foram alterados.
Um sistema de reserva de carros é um bom exemplo para explorar os benefícios de usar o EF Core com o MongoDB, pois há a necessidade de representar uma gama diversas de entidades. Haverá entidades como carros com status de disponibilidade e localização associados, e reservas incluindo o carro associado.
À medida que o sistema evolui e cresce, garantir a consistência dos dados pode se tornar um desafio. Além disso, à medida que os usuários interagem com o sistema, atualizações parciais nas entidades de dados, como detalhes da reserva ou especificações do carro, acontecerão com cada vez mais frequência. Capturar e lidar com eficiência com essas atualizações é fundamental para um bom desempenho do sistema e integridade dos dados.
Para acompanhar este tutorial, você precisará de algumas coisas:
- .NET 7.0.
- Conhecimento básico de ASP.NET MVC e C#.
Se você quiser apenas ver o código de exemplo, poderá visualizar o código completo no repositório do GitHub.
O ASP.NET Core é uma estrutura web muito flexível, permitindo que você crie diferentes tipos de aplicativos da Web que tenham pequenas diferenças em termos de interface ou estrutura. Para este tutorial, criaremos um projeto MVC que fará uso de arquivos e controladores estáticos. Existem outros tipos de frontend que você pode usar, como o React, mas o MVC com .cshtml views é o mais usado. Para criar o projeto, vamos usar o .NET CLI:
1 dotnet new mvc -o SuperCarBookingSystem
Como usamos o CLI, embora mais fácil, ele cria apenas o arquivo csproj e não o arquivo de solução que nos permite abri-lo no Visual Studio, então corrigiremos isso.
1 cd SuperCarBookingSystem 2 dotnet new sln 3 dotnet sln .\SuperCarBookingSystem.sln add .\SuperCarBookingSystem.csproj
Agora que criamos o novo projeto, queremos adicionar os pacotes NuGet necessários. Usando o Gerenciador de pacotes NuGet ou os comandos .NET CLI abaixo, adicione os pacotes MongoDB MongoDB.EntityFrameworkCore e Microsoft.EntityFrameworkCore.
1 dotnet add package MongoDB.EntityFrameworkCore --version 7.0.0-preview.1 2 dotnet add package Microsoft.EntityFrameworkCore
No momento em que este artigo foi escrito, o MongoDB.EntityFrameworkCore está em versão prévia, portanto, se estiver usando a interface do NuGet Package Manager dentro do Visual Studio, certifique-se de marcar a caixa “include pre-release”, caso contrário você não obterá nenhum resultado ao procurá-la.
Antes de podermos começar a implementar os novos pacotes que acabamos de adicionar, precisamos criar os modelos que representam as entidades que queremos em nosso sistema de reserva de carro que, é claro, serão armazenados no MongoDB Atlas como documentos. Nas seguintes subseções, criaremos os seguintes modelos:
- Carro
- Reserva
- MongoDBSettings
Primeiro, precisamos criar nosso modelo de carro que representará os carros que estão disponíveis para serem reservados em nosso sistema.
- Crie uma nova classe na pasta Modelos chamada Carro.
- Adicione o seguinte código:
1 using MongoDB.Bson; 2 using MongoDB.EntityFrameworkCore; 3 using System.ComponentModel.DataAnnotations; 4 5 6 namespace SuperCarBookingSystem.Models 7 { 8 [ ] 9 public class Car 10 { 11 12 public ObjectId Id { get; set; } 13 14 [ ]15 [ ]16 public string? Model { get; set; } 17 18 19 20 [ ]21 [ ]22 public string NumberPlate { get; set; } 23 24 25 [ ]26 public string? Location { get; set; } 27 28 29 public bool IsBooked { get; set; } = false; 30 } 31 }
O atributo da coleção antes da classe informa ao aplicativo qual coleção dentro do banco de dados estamos usando. Isso nos permite ter nomes diferentes ou maiúsculas entre nossa classe e nossa coleção, se quisermos.
Também precisamos criar uma classe de reserva para representar todas as reservas que recebemos em nosso sistema.
- Crie uma nova classe dentro da pasta Models chamada Booking.
- Adicione o seguinte código a ele:
1 using MongoDB.Bson; 2 using MongoDB.EntityFrameworkCore; 3 using System.ComponentModel.DataAnnotations; 4 5 6 namespace SuperCarBookingSystem.Models 7 { 8 [ ]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 [ ]21 [ ]22 public DateTime StartDate { get; set; } 23 24 25 [ ]26 [ ]27 public DateTime EndDate { get; set; } 28 } 29 }
Embora não seja um documento em nosso banco de dados, precisamos de uma classe de modelo para armazenar nossas configurações relacionadas ao MongoDB para que possam ser usadas em todo o aplicativo.
- Crie outra classe em Models chamada MongoDBSettings.
- Adicione o seguinte código:
1 public class MongoDBSettings 2 { 3 public string AtlasURI { get; set; } 4 public string DatabaseName { get; set; } 5 }
Essa é a parte emocionante. Vamos começar a implementar o EF Core e aproveitar o novo provedor MongoDB. Se você já estiver acostumado a trabalhar com o EF Core, parte disso será familiar.
- Em um local de sua escolha, crie uma classe chamada CarBookingDbContext. Coloquei-o dentro de uma nova pasta chamada Services.
- Substitua o código dentro do namespace pelo seguinte:
1 using Microsoft.EntityFrameworkCore; 2 using SuperCarBookingSystem.Models; 3 4 namespace 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 }
Se você está familiarizado com o EF Core, isso parecerá familiar. A classe estende o DbContext e criamos propriedades do DbSet que armazenam os modelos que também estarão presentes no banco de dados. Também substituímos o método OnModelCreating. Você pode notar que, diferentemente de quando usamos o SQL Server, não chamamos .toTable (). Em vez disso, poderíamos chamar ToCollection, mas isso não é necessário aqui, pois especificamos a coleção usando atributos na classe.
Anteriormente, criamos um modelo MongoDBSettings e agora precisamos adicionar os valores que as propriedades mapeiam em nossas appsettings.
- Tanto em appsettings.json quanto em appsettings.Development.json, adicione a seguinte nova seção:
1 "MongoDBSettings": { 2 "AtlasURI": "mongodb+srv://<username>:<password>@<url>", 3 "DatabaseName": "cargarage" 4 }
Agora que configuramos nossos modelos e DbContext, é hora de adicioná-los ao nosso arquivo program.cs.
Após a linha
builder.Services.AddControllersWithViews();
existente, adicione o seguinte código:1 var mongoDBSettings = builder.Configuration.GetSection("MongoDBSettings").Get<MongoDBSettings>(); 2 builder.Services.Configure<MongoDBSettings>(builder.Configuration.GetSection("MongoDBSettings")); 3 4 builder.Services.AddDbContext<CarBookingDbContext>(options => 5 options.UseMongoDB(mongoDBSettings.AtlasURI ?? "", mongoDBSettings.DatabaseName ?? ""));
Agora, é hora de adicionar os serviços que usaremos para nos comunicar com o banco de dados por meio do CarBookingDbContext que criamos. Para cada serviço, criaremos uma interface e a classe que a implementa.
A primeira interface e serviço que implementaremos é para realizar as operações CRUD na coleção de carros. Isso é conhecido como padrão de repositório. Você pode ver as pessoas interagindo diretamente com o DbContext. Mas a maioria das pessoas usa esse padrão, e é por isso que o incluímos aqui.
- Se você ainda não o fez, crie uma pasta Services para armazenar nossas novas classes.
- Crie uma interface ICarService e adicione o seguinte código para os métodos que vamos implementar:
1 using MongoDB.Bson; 2 using SuperCarBookingSystem.Models; 3 4 namespace 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 } - Crie um arquivo de classe CarService.
- Atualize a declaração da classe CarService para que ela implemente o ICarService que acabamos de criar:
1 using Microsoft.EntityFrameworkCore; 2 using MongoDB.Bson; 3 using MongoDB.Driver; 4 using SuperCarBookingSystem.Models; 5 6 namespace SuperCarBookingSystem.Services 7 { 8 public class CarService : ICarService 9 { - Isso fará com que um rabisco vermelho apareça abaixo do ICarService, pois ainda não implementamos todos os métodos, mas vamos implementar os métodos um a um.
- Adicione o código a seguir após a declaração de classe que adiciona um objeto CarBookingDbContext local e um construtor que obtém uma instância do DbContext via injeção de dependência.
1 private readonly CarBookingDbContext _carDbContext; 2 public CarService(CarBookingDbContext carDbContext) 3 { 4 _carDbContext = carDbContext; 5 } - Em seguida, implementaremos o método GetAllCars, então adicione o seguinte código:
1 public IEnumerable<Car> GetAllCars() 2 { 3 return _carDbContext.Cars.OrderBy(c => c.Id).AsNoTracking().AsEnumerable<Car>(); 4 } A propriedade id aqui mapeia para o campo _id em nosso documento, que é um tipo especial do MongoDB ObjectId e é gerado automaticamente quando um novo documento é criado. Mas o que é útil na propriedade _id é que ela pode realmente ser usada para ordenar documentos devido a como é gerada nos bastidores.Se você nunca viu isso antes, o métodoAsNoTracking()
faz parte do EF Core e impede que o EF rastreie as alterações que você faz em um objeto. Isso é útil para leituras quando você sabe que não haverá nenhuma alteração. - Em seguida, implementamos o método para obter um carro específico usando sua propriedade ID:
1 public Car? GetCarById(ObjectId id) 2 { 3 return _carDbContext.Cars.FirstOrDefault(c => c.Id == id); 4 } Depois, adicionamos a implementação do AddCar:1 public 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 } Em um ambiente de produção, convém usar algo como ILogger para rastrear essas alterações em vez de imprimir no console. Mas isso nos permitirá ver claramente que uma nova entidade foi adicionada, mostrando o rastreamento de mudanças em ação. - EditCar é o próximo:
1 public 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 } Novamente, adicionamos uma chamada para imprimir informações do rastreamento de alterações, pois elas mostrarão que o novo EF Core Provider, mesmo ao usar o MongoDB como banco de dados, é capaz de rastrear modificações. - Por fim, precisamos implementar DeleteCar:
1 public 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 }
Em seguida, temos o IBookingService e o BookingService.
- Crie a interface IBookingService e adicione os seguintes métodos:
1 using MongoDB.Bson; 2 using SuperCarBookingSystem.Models; 3 namespace 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 } - Crie a classe BookingService e substitua sua classe pelo seguinte código que implementa todos os métodos:
1 using Microsoft.EntityFrameworkCore; 2 using MongoDB.Bson; 3 using SuperCarBookingSystem.Models; 4 5 namespace 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 }
Este código é muito semelhante ao código para a classe CarService, só que para reservas.
A etapa final para os serviços é adicioná-los ao contêiner de injeção de dependência.
Dentro do Program.cs, Adicione o seguinte código após o código que adicionamos lá antes:
1 builder.Services.AddScoped<ICarService, CarService>(); 2 builder.Services.AddScoped<IBookingService, BookingService>();
Antes de implementarmos o front-end, precisamos adicionar os modelos de visualização que atuarão como mensageiros entre o front-end e o back-end, quando necessário. Embora nosso aplicativo seja bastante simples, a implementação do modelo de visualização ainda é uma boa prática, pois ajuda a desacoplar as partes do aplicativo.
O primeiro que adicionaremos é o CarListViewModel. Ele será usado como modelo em nossa página Razor mais tarde para listar os carros do nosso banco de dados.
- Crie uma nova pasta na raiz do projeto chamada ViewModels.
- Adicione uma nova classe chamada CarListViewModel.
- Adicione
public IEnumerable<Car> Cars { get; set; }
dentro da sua classe.
Também queremos um modelo de visualização que possa ser usado pela visualização Add que adicionaremos mais tarde.
- Dentro da pasta ViewModels, crie uma nova classe chamada CarAddViewModel.
- Adicione
public Car? Car { get; set; }
.
Agora, queremos fazer algo muito semelhante para reservas, começando com BookingListViewModel.
- Crie uma nova classe na pasta ViewModels chamada BookingListViewModel.
- Adicione
public IEnumerable<Booking> Bookings { get; set; }
.
Finalmente, temos nosso BookingAddViewModel.
Crie a classe e adicione a propriedade
public Booking? Booking { get; set; }
dentro da classe.Posteriormente, adicionaremos referências aos nossos modelos e viewmodels nas visualizações. Para que o aplicativo saiba quais são, precisamos adicionar referências a eles no arquivo _ViewImports.cshtml dentro da pasta Visualizações.
Já haverá algumas referências lá, incluindo TagHelpers, então queremos adicionar referências às nossas pastas .Models e .ViewModels. Quando adicionada, ela ficará parecida com o que está abaixo, apenas com o nome do seu aplicativo.
1 @using <YourApplicationName> 2 @using <YourApplicationName>.Models 3 @using <YourApplicationName>.ViewModels
Agora que temos a implementação de back-end e os modelos de visualização aos quais faremos referência, podemos começar a trabalhar no front-end. Criaremos dois controladores: um para Car (carro) e outro para Booking (reserva).
O primeiro controlador que adicionaremos é para o carro.
- Dentro da pasta Controllers existente, adicione um novo controlador. Se estiver usando o Visual Studio, use o modelo Controlador MVC – Controlador vazio.
- Adicione um objeto ICarService local e um construtor que o obtém da injeção de dependência:
1 private readonly ICarService _carService; 2 3 4 public CarController(ICarService carService) 5 { 6 _carService = carService; 7 } - Dependendo do que foi fornecido com seu controlador de andaimes, crie ou atualize a função Index com o seguinte:
1 public IActionResult Index() 2 { 3 CarListViewModel viewModel = new() 4 { 5 Cars = _carService.GetAllCars(), 6 }; 7 return View(viewModel); 8 } Para as outras operações CRUD – criar, atualizar e excluir – teremos dois métodos para cada uma: um para Get e outro para Post. - O HttpGet para Add será muito simples, porque não precisa passar nenhum dado:
1 public IActionResult Add() 2 { 3 return View(); 4 } - Em seguida, adicione o método Adicionar que será chamado quando for solicitada a adição de um novo carro:
1 [ ]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 } - Agora, vamos adicionar o código para editar um carro:
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 [ ]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 } - Por fim, temos Excluir:
1 public 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 [ ]14 public 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 }
Agora, para o controlador de reserva. É muito semelhante ao CarController, mas tem uma referência tanto ao carro quanto ao serviço de reserva, pois precisamos associar um carro a uma reserva. Isso ocorre porque no momento o EF Core Provider não oferece suporte a relacionamentos entre entidades, portanto podemos relacionar entidades de uma maneira diferente. No entanto, você pode visualizar o roteiro no repositório do GitHub.
- Crie outro Controlador MVC vazio chamado BookingController.
- Cole o código a seguir substituindo a classe atual:
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 [ ]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 [ ]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 [ ]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 }
Agora que temos o back-end e os controladores preparados com os endpoints para nosso sistema de reserva de carros, é hora de implementar as visualizações. Isso será feito usando as páginas Razor. Você também verá referências a classes do Bootstrap, pois essa é a estrutura CSS que vem com aplicativos MVC prontos para uso. Forneceremos visualizações para as operações CRUD tanto para listagens quanto para reservas.
Primeiro, forneceremos uma visualização que mapeará para a raiz de /Car que, por convenção, examinará o método de índice que implementamos.
O ASP.NET Core MVC usa um padrão de convenção pelo qual você dá ao arquivo .cshtml o nome do endpoint/método que ele usa e ele fica dentro de uma pasta com o nome de seu controlador.
- Dentro da pasta Visualizações, crie uma nova subpasta chamada Car.
- Dentro da pasta Car, adicione uma nova visualização. Se estiver usando os modelos disponíveis, é Razor View - Empty. Dê um nome ao índice de visualização.
- Exclua o conteúdo do arquivo e adicione uma referência ao CarListViewModel na parte superior
@model CarListViewModel
. - Em seguida, devemos adicionar um placeholder para o tratamento de erros. Se houve algum problema ao excluir um carro, nós adicionamos uma string ao TempData e, portanto, temos que adicionar isso à visualização, se houver dados a serem exibidos.
1 @if (TempData["CarDeleted"] != null) 2 { 3 <p class="text-success">@TempData["CarDeleted"]</p> 4 } - A seguir, trataremos se não há carros no banco de dados, exibindo uma mensagem ao usuário:
1 @if (!Model.Cars.Any()) 2 { 3 <p>No results</p> 4 } - A maneira mais fácil de exibir a lista de carros e as informações relevantes é por meio de uma tabela:
1 else 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> Faz sentido ter a lista de carros como nossa página inicial, portanto, antes de prosseguirmos, atualizaremos a rota padrão de Home para /Car. - Em Program.cs, dentro de
app.MapControllerRoute
, substitua a linha padrão pelo seguinte:1 pattern: "{controller=Car}/{action=Index}/{id?}");
Se executássemos isso agora, os botões levariam a 404s porque ainda não os implementamos. Então, vamos fazer isso agora.
Começaremos com o formulário para adicionar novos carros.
- Adicione uma nova Razor View vazia dentro da subpasta Car chamada Add.cshtml.
- Antes de adicionar o formulário, adicionaremos a referência do modelo na parte superior, um cabeçalho e conteúdo condicional para a mensagem de erro.
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 } - Agora, podemos implementar o formulário.
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>
Agora, queremos adicionar um botão na parte inferior para navegar facilmente de volta à lista de carros, caso o usuário decida não adicionar um novo carro. Adicione o seguinte após a tag
</form>
:1 <div> 2 <a asp-controller="Car" asp-action="Index">Back to list</a> 3 </div>
O código para a página Editar é quase idêntico a Adicionar, mas ele usa o carro como um modelo, pois usará o carro que é passado para pré-preencher o formulário para edição.
- Adicione outra visualização dentro da subpasta do carro chamada Edit.cshtml.
- Adicione o seguinte código:
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>
A última página que precisamos implementar é a página chamada assim que clicamos no botão de exclusão de um carro.
- Crie uma nova visualização vazia chamada Delete.cshtml.
- Adicione o seguinte código para adicionar o modelo, o título e a mensagem de erro condicional:
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 } Em vez de um formulário como nas outras exibições, vamos adicionar uma lista de descrição para exibir informações sobre o carro para o qual estamos confirmando a exclusão.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> - Abaixo disso, adicionaremos um formulário para enviar a exclusão e o botão para retornar à lista:
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>
Adicionamos as visualizações para os carros, então agora adicionaremos as visualizações para as reservas, começando pela listagem de todos os livros existentes.
- Crie uma nova pasta dentro da pasta Views chamada Booking.
- Crie uma nova visualização vazia chamada Index.
- Adicione o seguinte código para exibir as reservas, se houver:
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 16 else 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 }
Adicionar reservas é o próximo passo. Esta visualização estará disponível quando o botão de reserva for clicado ao lado de um carro listado.
- Crie uma visualização vazia chamada Add.cshtml.
- Adicione o seguinte código:
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>
Assim como acontece com os carros, também queremos poder editar livros existentes.
- Crie uma visualização vazia chamada Edit.cshtml.
- Adicione o seguinte código:
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>
A visualização final que precisamos adicionar é excluir uma reserva. Assim como no caso dos carros, exibiremos as informações da reserva e a confirmação da exclusão.
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>
Agora temos um aplicativo funcional que usa o novo provedor do MongoDB para EF Core — oba! Agora é a hora de testar tudo e visitar nossos endpoints para garantir que tudo funcione.
Não faz parte deste tutorial, pois não é obrigatório, mas optei por fazer algumas alterações no arquivo site.css para dar um toque especial. Também atualizei o arquivo _Layout.cshtml para adicionar as páginas Carro e Reservas à barra de navegação. Você verá isso refletido nas capturas de tela no restante do artigo. É claro que você pode fazer suas próprias alterações se tiver ideias de como gostaria de deixar o aplicativo.
Abaixo estão algumas capturas de tela que fiz no aplicativo, mostrando os recursos do endpoint Carros.
As páginas de reservas serão muito semelhantes às dos carros, mas são adaptadas para o modelo de reservas que inclui datas.
Pronto: temos um aplicativo de pilha completa usando ASP.NET MVC que usa o novo MongoDB Provider para EF Core. Podemos realizar as operações CRUD e acompanhar as alterações. O EF Core é amplamente usado entre desenvolvedores, portanto, ter um MongoDB Provider oficial é muito emocionante. Esta biblioteca está em Pré-visualização, o que significa que continuamos desenvolvendo novos recursos. Fique atento às atualizações e estamos sempre abertos a comentários. Mal podemos esperar para ver suas criações!
Você pode visualizar o Roadmap do provedor no repositório GitHub, onde também pode encontrar links para a documentação!
Como sempre, se você tiver alguma dúvida sobre esse ou outros assuntos, participe dos fóruns da MongoDB Community.