Search code examples
c#asp.net-coreentity-framework-corenpgsql

ASP.NET Core 6 MVC - EF Core 6 - model is not validating correctly


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:

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" class="text-danger"></div>
            <input asp-for="StudiengangName" class="form-control" />
            <span asp-validation-for="StudiengangName" class="text-danger"></span>
        </td>
        <td>
            <select asp-for="FakultaetID" class="form-control" asp-items="ViewBag.FakultaetId">
                <option disabled="disabled" selected="selected" value="0">Bitte wählen...</option>
            </select>
            <span asp-validation-for="FakultaetID" class="text-danger"></span>
        </td>
        <td>
            <input type="submit" value="Speichern" class="btn btn-outline-success btn-sm" id="btn-addinline-submit" />
            <input type="reset" onClick="location.reload()" class="btn btn-outline-danger btn-sm" 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?


Solution

  • 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.