To give you some context I am currently learning how to create an ASP.NET Core MVC app, and I am having trouble figuring out how controllers actually work, and how EF handles relationships.
Right now I have these two tables:
public class Persona
{
public int Id { get; set; }
public string Nombre { get; set; }
public string ApellidoPaterno { get; set; }
public string ApellidoMaterno { get; set; }
public string Email { get; set; }
public string Telefono { get; set; }
// Relationship
public int? UsuarioId { get; set; }
public Usuario? Usuario { get; set; }
}
public class Usuario
{
public int Id { get; set; }
public string Username { get; set; }
public string Password { get; set; }
// Relacion
public int? PersonaId { get; set; }
[ForeignKey("PersonaId")]
public Persona? Persona { get; set; }
}
These two tables have a one to one relationship to which I have created a view model that will be used in order to display and edit both of them at the same time.
This is the view model I have created:
public class PersonaUsuarioVista
{
// Persona properties
public string Nombre { get; set; }
public string ApellidoPaterno { get; set; }
public string ApellidoMaterno { get; set; }
public string Email { get; set; }
public string Telefono { get; set; }
// Usuario properties
public string Username { get; set; }
public string Password { get; set; }
}
This is the view model I have used to correctly display and create new Personas
and Usuarios
.
But when It comes to the Edit. I am not sure why it isn't working.
The Get
method works correctly, meaning it does display the data inside the input fields.
But once a change is made and saved... Nothing I am not sure why this is the case.
This is the Post
method for the edit:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, UsuarioPersonaVista.PersonaUsuarioVista model)
{
if (ModelState.IsValid)
{
var personaToUpdate = await _context.Personas.FindAsync(id);
if (personaToUpdate == null)
{
return NotFound();
}
var usuarioToUpdate = await _context.Usuarios.FirstOrDefaultAsync(u => u.PersonaId == id);
if (usuarioToUpdate == null)
{
return NotFound();
}
// Update Persona properties
personaToUpdate.Nombre = model.Nombre;
personaToUpdate.ApellidoPaterno = model.ApellidoPaterno;
personaToUpdate.ApellidoMaterno = model.ApellidoMaterno;
personaToUpdate.Email = model.Email;
personaToUpdate.Telefono = model.Telefono;
// Update Usuario properties
usuarioToUpdate.Username = model.Username;
// Save changes
_context.Usuarios.Update(usuarioToUpdate);
_context.Personas.Update(personaToUpdate);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(model); // Return the view with the model if the ModelState is invalid
}
And this is the Razor page itself:
<form asp-controller="Persona" asp-action="Edit" method="post">
<div>
<label for="Nombre">Nombre</label>
<input type="text" asp-for="Nombre" />
<span asp-validation-for="Nombre"></span>
</div>
<div>
<label for="ApellidoPaterno">Apellido Paterno</label>
<input type="text" asp-for="ApellidoPaterno" />
<span asp-validation-for="ApellidoPaterno"></span>
</div>
<div>
<label for="ApellidoMaterno">Apellido Materno</label>
<input type="text" asp-for="ApellidoMaterno" />
<span asp-validation-for="ApellidoMaterno"></span>
</div>
<div>
<label for="Email">Email</label>
<input type="text" asp-for="Email" />
<span asp-validation-for="Email"></span>
</div>
<div>
<label for="Telefono">Telefono</label>
<input type="text" asp-for="Telefono" />
<span asp-validation-for="Telefono"></span>
</div>
<div>
<label for="Username">Username</label>
<input type="text" asp-for="Username" />
<span asp-validation-for="Username"></span>
</div>
<input type="submit" value="Editar">
</form>
Any help or guidance towards solving this problem or just understanding better what I did wrong is highly appreciated. Thank you very much!
Let's break this down and see how we can resolve the issue with your Edit method in your MVC .NET Core application.
Understanding the Controllers and EF Core Relationships First, let's make sure we understand how Entity Framework (EF) handles relationships and how controllers work in this context.
Entity Framework Relationships In EF Core, relationships between entities are defined using navigation properties and foreign keys. In your case, the Persona and Usuario classes have a one-to-one relationship:
Persona has a foreign key to Usuario (UsuarioId).
Usuario has a foreign key to Persona (PersonaId).
View Model The PersonaUsuarioVista view model combines properties from both Persona and Usuario for display and editing purposes.
Issue in Edit Method You mentioned that while the Get method works correctly, the Post method does not update the records as expected. Let's break down your Post method:
csharp
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, UsuarioPersonaVista.PersonaUsuarioVista model)
{
if (ModelState.IsValid)
{
var personaToUpdate = await _context.Personas.FindAsync(id);
if (personaToUpdate == null)
{
return NotFound();
}
var usuarioToUpdate = await _context.Usuarios.FirstOrDefaultAsync(u => u.PersonaId == id);
if (usuarioToUpdate == null)
{
return NotFound();
}
// Update Persona properties
personaToUpdate.Nombre = model.Nombre;
personaToUpdate.ApellidoPaterno = model.ApellidoPaterno;
personaToUpdate.ApellidoMaterno = model.ApellidoMaterno;
personaToUpdate.Email = model.Email;
personaToUpdate.Telefono = model.Telefono;
// Update Usuario properties
usuarioToUpdate.Username = model.Username;
// Save changes
_context.Usuarios.Update(usuarioToUpdate);
_context.Personas.Update(personaToUpdate);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(model);
}
Potential Issues Foreign Key Relationship:
Ensure that the relationships between Persona and Usuario are properly configured.
When updating related entities, make sure that the foreign key values are correctly set.
Tracking Changes:
EF Core tracks changes to entities. Ensure that the entities retrieved (personaToUpdate and usuarioToUpdate) are the same instances being updated and saved.
Password Update:
If you're not updating the Password, ensure it is not cleared out during the update.
Suggested Changes Include Password in the ViewModel (if needed):
csharp
public class PersonaUsuarioVista
{
// Persona properties
public string Nombre { get; set; }
public string ApellidoPaterno { get; set; }
public string ApellidoMaterno { get; set; }
public string Email { get; set; }
public string Telefono { get; set; }
// Usuario properties
public string Username { get; set; }
public string Password { get; set; } // Include this if the password needs updating
}
Ensure Changes are Tracked Properly:
csharp
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, UsuarioPersonaVista.PersonaUsuarioVista model)
{
if (ModelState.IsValid)
{
var personaToUpdate = await _context.Personas.FindAsync(id);
if (personaToUpdate == null)
{
return NotFound();
}
var usuarioToUpdate = await _context.Usuarios.FirstOrDefaultAsync(u => u.PersonaId == id);
if (usuarioToUpdate == null)
{
return NotFound();
}
// Update Persona properties
personaToUpdate.Nombre = model.Nombre;
personaToUpdate.ApellidoPaterno = model.ApellidoPaterno;
personaToUpdate.ApellidoMaterno = model.ApellidoMaterno;
personaToUpdate.Email = model.Email;
personaToUpdate.Telefono = model.Telefono;
// Update Usuario properties
usuarioToUpdate.Username = model.Username;
if (!string.IsNullOrWhiteSpace(model.Password))
{
usuarioToUpdate.Password = model.Password; // Only update if Password is provided
}
// Save changes
_context.Entry(personaToUpdate).State = EntityState.Modified;
_context.Entry(usuarioToUpdate).State = EntityState.Modified;
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(model);
}
Razor Page Adjustment Ensure the Razor page includes the hidden field for the Id:
html
<form asp-controller="Persona" asp-action="Edit" method="post">
<input type="hidden" asp-for="Id" />
<div>
<label for="Nombre">Nombre</label>
<input type="text" asp-for="Nombre" />
<span asp-validation-for="Nombre"></span>
</div>
<div>
<label for="ApellidoPaterno">Apellido Paterno</label>
<input type="text" asp-for="ApellidoPaterno" />
<span asp-validation-for="ApellidoPaterno"></span>
</div>
<div>
<label for="ApellidoMaterno">Apellido Materno</label>
<input type="text" asp-for="ApellidoMaterno" />
<span asp-validation-for="ApellidoMaterno"></span>
</div>
<div>
<label for="Email">Email</label>
<input type="text" asp-for="Email" />
<span asp-validation-for="Email"></span>
</div>
<div>
<label for="Telefono">Telefono</label>
<input type="text" asp-for="Telefono" />
<span asp-validation-for="Telefono"></span>
</div>
<div>
<label for="Username">Username</label>
<input type="text" asp-for="Username" />
<span asp-validation-for="Username"></span>
</div>
<div>
<label for="Password">Password</label>
<input type="password" asp-for="Password" />
<span asp-validation-for="Password"></span>
</div>
<input type="submit" value="Editar">
</form>
Summary
In summary, the main points are:
Make sure to set the state of the entities to EntityState.Modified.
Ensure that the Password field is correctly handled.
Include a hidden field for the Id in your form.
Great, let's build on what we've discussed and create a controller that will handle the PersonaUsuarioVista ViewModel in Razor.
Step-by-Step Guide
Here's an example of how your PersonaController might look:
csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using YourNamespace.Models;
using YourNamespace.ViewModels;
public class PersonaController : Controller
{
private readonly YourDbContext _context;
public PersonaController(YourDbContext context)
{
_context = context;
}
// GET: Persona/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var persona = await _context.Personas
.Include(p => p.Usuario)
.FirstOrDefaultAsync(m => m.Id == id);
if (persona == null)
{
return NotFound();
}
var viewModel = new PersonaUsuarioVista
{
Nombre = persona.Nombre,
ApellidoPaterno = persona.ApellidoPaterno,
ApellidoMaterno = persona.ApellidoMaterno,
Email = persona.Email,
Telefono = persona.Telefono,
Username = persona.Usuario?.Username
// Password is not included here for security reasons, handle password updates separately
};
return View(viewModel);
}
// POST: Persona/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, PersonaUsuarioVista model)
{
if (id != model.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
var personaToUpdate = await _context.Personas.FindAsync(id);
if (personaToUpdate == null)
{
return NotFound();
}
var usuarioToUpdate = await _context.Usuarios.FirstOrDefaultAsync(u => u.PersonaId == id);
if (usuarioToUpdate == null)
{
return NotFound();
}
// Update Persona properties
personaToUpdate.Nombre = model.Nombre;
personaToUpdate.ApellidoPaterno = model.ApellidoPaterno;
personaToUpdate.ApellidoMaterno = model.ApellidoMaterno;
personaToUpdate.Email = model.Email;
personaToUpdate.Telefono = model.Telefono;
// Update Usuario properties
usuarioToUpdate.Username = model.Username;
if (!string.IsNullOrWhiteSpace(model.Password))
{
usuarioToUpdate.Password = model.Password; // Ensure password update is secure
}
try
{
_context.Update(personaToUpdate);
_context.Update(usuarioToUpdate);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!PersonaExists(personaToUpdate.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(model);
}
private bool PersonaExists(int id)
{
return _context.Personas.Any(e => e.Id == id);
}
}
Here is how your Edit.cshtml might look:
html
@model YourNamespace.ViewModels.PersonaUsuarioVista
<h2>Edit Persona and Usuario</h2>
<form asp-action="Edit">
<input type="hidden" asp-for="Id" />
<div>
<label asp-for="Nombre"></label>
<input asp-for="Nombre" class="form-control" />
<span asp-validation-for="Nombre" class="text-danger"></span>
</div>
<div>
<label asp-for="ApellidoPaterno"></label>
<input asp-for="ApellidoPaterno" class="form-control" />
<span asp-validation-for="ApellidoPaterno" class="text-danger"></span>
</div>
<div>
<label asp-for="ApellidoMaterno"></label>
<input asp-for="ApellidoMaterno" class="form-control" />
<span asp-validation-for="ApellidoMaterno" class="text-danger"></span>
</div>
<div>
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div>
<label asp-for="Telefono"></label>
<input asp-for="Telefono" class="form-control" />
<span asp-validation-for="Telefono" class="text-danger"></span>
</div>
<div>
<label asp-for="Username"></label>
<input asp-for="Username" class="form-control" />
<span asp-validation-for="Username" class="text-danger"></span>
</div>
<div>
<label asp-for="Password"></label>
<input asp-for="Password" class="form-control" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
<div>
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
Summary
Controller: We created the PersonaController with Edit actions to handle both GET and POST requests.
View: The Razor view Edit.cshtml uses the PersonaUsuarioVista ViewModel to display and edit both Persona and Usuario properties.