I'm currently trying to start a new project with ASP.NET Core 6 MVC and Entity Framework Core 6 and npgsql.
When I try to add one entity which has a foreign identity the ModelState.IsValid
keeps returning false - as the model doesn't expand the foreign entity.
Basically I followed the official documentation at:
- https://docs.microsoft.com/de-de/aspnet/core/data/ef-mvc/complex-data-model?view=aspnetcore-6.0
- https://docs.microsoft.com/de-de/aspnet/core/data/ef-mvc/update-related-data?view=aspnetcore-6.0
So my classes look like:
namespace PV.Models
{
public class Fakultaet
{
[Key]
public int FakultaetID { get; set; }
[Required]
public string FakuName { get; set; }
}
public class Studiengang
{
[Key]
public int StudiengangID { get; set; }
[Required]
public string StudiengangName { get; set;}
[Required,ForeignKey("Fakultaet")]
public int FakultaetID { get; set; }
public Fakultaet Fakultaet { get; set; }
}
}
Partial view:
@model PV.Models.Studiengang
<tr>
<td>
<div asp-validation-summary="ModelOnly" ></div>
<input asp-for="StudiengangName" />
<span asp-validation-for="StudiengangName" ></span>
</td>
<td>
<select asp-for="FakultaetID" asp-items="ViewBag.FakultaetId">
<option disabled="disabled" selected="selected" value="0">Bitte wählen...</option>
</select>
<span asp-validation-for="FakultaetID" ></span>
</td>
<td>
<input type="submit" value="Speichern" id="btn-addinline-submit" />
<input type="reset" onClick="location.reload()" id="btn-addinline-abort" value="Abbrechen" />
</td>
</tr>
Controller:
namespace PV.Controllers
{
public class StudiengangController : Controller
{
private readonly PraktikumsKontext _context;
public StudiengangController(PraktikumsKontext ctx)
{
_context = ctx;
}
// --- snip ---
// GET: Student/Add
public IActionResult AddStudiengangInline()
{
ViewBag.FakultaetId = new SelectList(_context.Fakultaeten.AsNoTracking(), "FakultaetID", "FakuName");
return PartialView();
}
// POST: Student/Add
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddStudiengangInline([Bind("StudiengangName, FakultaetID")] Studiengang studiengang )
{
if (ModelState.IsValid)
{
_context.Add(studiengang);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewData["FakultaetId"] = new SelectList(_context.Fakultaeten, "FakultaetID", "FakuName", studiengang.FakultaetID);
return PartialView(studiengang);
}
}
}
When I now fill out my form and POST StudiengangName=Test1234;FakultaetID=1
(with an existing Fakultaet
with ID = 1
of course) my model does look like this:
StudiengangID = 0
StudiengangName = "Test1234"
Fakultaet = null
FakultaetID = 1
Therefore the ModelState.IsValid
returns false
as Fakultaet
is null
.
Here I'd assume that EF Core 6 does its magic and resolves the entity I'm referencing.
If I add the following snippet before checking if the model is valid, everything seems to work:
studiengang.Fakultaet =
_context.Fakultaeten.SingleOrDefault(stg => stg.FakultaetID == studiengang.FakultaetID);
ModelState.ClearValidationState(nameof(Fakultaet));
TryValidateModel(studiengang);
But this seems to be a dirty workaround as it was not necessary in .NET Core 3.1 with almost the same setup.
Does anyone have an idea what I'm missing?
CodePudding user response:
you have to add form with validation and antiforgery to your view
@model PV.Models.Studiengang
<form method="post" asp-controller="Studiengang" asp-action="AddStudiengangInline" >
@Html.AntiForgeryToken()
@Html.ValidationSummary(false, "", new { @class = "text-danger" })
<tr>
,,,,,
<td>
<input type="submit" value="Speichern" id="btn-addinline-submit" />
...
</td>
</tr>
</form>
It is better to add a list of DDL items to viewModel
public class Studiengang
{
....
public int FakultaetID { get; set; }
public Fakultaet Fakultaet { get; set; }
[NotMapped]
public List<SelectListItem> FakultaetItems { get; set; }
}
action
public IActionResult AddStudiengangInline()
{
var model= new Studiengang { FakultaetItems = new _context.Fakultaeten
.Select (i=> new SelectListItem { Value=i.FakultaetID, Text=i.FakuName}).ToList();
return PartialView(model);
}
IMHO remove Bind from post action
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> AddStudiengangInline(Studiengang studiengang )
and finaly a select control
<select asp-for="FakultaetID" asp-items="@Model.FakultaetItems"> </select>
CodePudding user response:
As this document said:
Beginning with .NET 6, new projects include the
<Nullable>enable</Nullable>
element in the project file. Once the feature is turned on, existing reference variable declarations become non-nullable reference types.
In .NET 6 the non-nullable property must be required, otherwise the ModelState will be invalid.
To achieve your requirement, you can remove <Nullable>enable</Nullable>
from your project file.
The second way, you can initialize the model like below:
public class Studiengang
{
[Key]
public int StudiengangID { get; set; }
[Required]
public string StudiengangName { get; set;}
[Required,ForeignKey("Fakultaet")]
public int FakultaetID { get; set; }
public Fakultaet Fakultaet { get; set; } = new Fakultaet();
}
The third way, you can add ?
to allow nullable:
public class Studiengang
{
[Key]
public int StudiengangID { get; set; }
[Required]
public string StudiengangName { get; set;}
[Required,ForeignKey("Fakultaet")]
public int FakultaetID { get; set; }
public Fakultaet? Fakultaet { get; set; }
}
The last way is like what you did to remove the key in model validation.
CodePudding user response:
Try using an InputModel with only the StudentId, StudentName, FacultyId and a collection of all faculties, populated from the database, as properties and pass that model to the dbContext.