Si las clases contienen secretos, aunque estos no se muestren en la web, hay maneras de introducir datos en un Creation aunque el form no tenga un campo para ello.
Ejemplo de clase con secretos Student.cs
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
// an atacker may set a Secret on creation ...
public string Secret { get; set; }
// ... or he may enroll the Student in Courses he shouldn't
public ICollection<Enrollment> Enrollments { get; set; } = [];
}
Hay varias alternativas para evitar esto:
- Usar ViewModels
- Usar
TryUpdateModelAsync
ViewModels
Una buena práctica es usar ViewModels (se puede meter en la misma Student.cs)
public class StudentVM
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
}
Y operamos a través del ViewModel en las vistas para crear un nuevo Student, por lo que no pueden meter un secreto ni otras relaciones.
[BindProperty]
public StudentVM StudentVM { get; set; }
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var entry = _context.Add(new Student());
entry.CurrentValues.SetValues(StudentVM);
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
Para que esto funcione las clases no tienen porque estar relacionadas, pero sus properties si que tienen que ser iguales.
Ahora en la vista usamos StudentVM en vez de Student
@page
@model CreateVMModel
@{
ViewData["Title"] = "Create";
}
<h1>Create</h1>
<h4>Student</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="StudentVM.LastName" class="control-label"></label>
<input asp-for="StudentVM.LastName" class="form-control" />
<span asp-validation-for="StudentVM.LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.FirstMidName" class="control-label"></label>
<input asp-for="StudentVM.FirstMidName" class="form-control" />
<span asp-validation-for="StudentVM.FirstMidName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="StudentVM.EnrollmentDate" class="control-label"></label>
<input asp-for="StudentVM.EnrollmentDate" class="form-control" />
<span asp-validation-for="StudentVM.EnrollmentDate" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-page="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
TryUpdateModelAsync
Una alternativa si no queremos tener que mantener los ViewModels es usar TryUpdateModelAsync para indicar especificamente los campos que queremos.
(reemplazamos el create por esto)
public async Task<IActionResult> OnPostAsync()
{
Student emptyStudent = new();
if(await TryUpdateModelAsync<Student>(
emptyStudent,
"student", // prefix for form value
s => s.FirstMidName, s => s.LastName, s => s.EnrollmentDate))
{
context.Student.Add(emptyStudent);
await context.SaveChangesAsync();
return RedirectToPage("./Index");
}
return Page();
}
Reference(s)
Part 2: Razor Pages - EF Core in ASP.NET, CRUD | Microsoft Learn