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

Time:12-20

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" ></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.

  • Related