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

Invalid model state in OnPost with foreign key


My dev environment is ASP.NET Core 8.0 Razor Pages with EF Core and SQL Server in Visual Studio 2022.

I have two classes, Event and EventType. An Event can have one EventType.

My model classes:

public class Event
{
     public int Id { get; set; }

     [DisplayName("Active")]
     public required bool IsActive { get; set; }

     public required string Title { get; set; }
     public string? SubTitle { get; set; }
     public required string Description { get; set; }

     [DisplayName("Start Date")]
     [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
     public DateOnly StartDate { get; set; }

     [DisplayName("End Date")]
     [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
     public DateOnly EndDate { get; set; }

     [DisplayName("Start Time")]
     [DisplayFormat(DataFormatString = "{0:hh:mm tt}", ApplyFormatInEditMode = true)]
     public required TimeOnly StartTime { get; set; }

     [DisplayName("End Time")]
     [DisplayFormat(DataFormatString = "{0:hh:mm tt}", ApplyFormatInEditMode = true)]
     public TimeOnly? EndTime { get; set; }

     [DisplayName("Registration Ends")]
     [DisplayFormat(DataFormatString = "{0:hh:mm tt}", ApplyFormatInEditMode = true)]
     public required TimeOnly RegistrationEndTime { get; set; }

     [DisplayName("Buy In")]
     [DisplayFormat(DataFormatString = "{0:C2}", ApplyFormatInEditMode = true)]
     public decimal? BuyIn { get; set; }

     [DisplayName("Chip Count")]
     [DisplayFormat(DataFormatString = "{0:C0}", ApplyFormatInEditMode = true)]
     public decimal? ChipCount { get; set; }

     // New property for Blinds, in minutes
     [DisplayName("Blinds (Minutes)")]
     public int? Blinds { get; set; }

     // Custom property to display Blind duration with "minutes" suffix
     [NotMapped]
     public string BlindDisplay => $"{Blinds} minutes";

     public string? Image { get; set; }

     //Navigation Properties
     [ForeignKey("Event")]
     public required int EventTypeId { get; set; }
     [DisplayName("Event Type")]
     public required EventType EventType { get; set; }
}

public class EventType
{
     public int Id { get; set; }
     public required string Description { get; set; }

     [StringLength(7, MinimumLength = 7, ErrorMessage = "Color must be exactly 7 characters long.")]
     [RegularExpression("^#[a-f0-9]{6}$", ErrorMessage = "Color must start with '#' and be followed by 6 lowercase hexadecimal characters.")]
     [DisplayName("Hex Color")]
     public required string Color { get; set; }
}

My code correctly populates a SelectList with EventType (EventTypeSelectList).

Issue: the OnPost method fails model validation when EventType is null.

Here is the OnPost method:

 public async Task<IActionResult> OnPostAsync()
 {
     if (!ModelState.IsValid)
     {
         return Page();
     }

     _context.Events.Add(Events);

     await _context.SaveChangesAsync();

     TempData["success"] = "Event Type database entry successfully created.";

     return RedirectToPage("./Index");
 }

Here is the pertinent part of the Create.csthml form

<form method="post">
    <div class="form-group">
        <label asp-for="Events.EventTypeId" class="control-label"></label>
        <select asp-for="Events.EventTypeId" class="form-control" asp-items="Model.EventTypeSelectList"></select>
        <span asp-validation-for="Events.EventType.Id" class="text-danger"></span>
    </div>

    <button type="submit" class="btn btn-primary my-3">Submit</button>
</form>

Solution

  • As stated in this document

    The validation system treats non-nullable parameters or bound properties as if they had a [Required(AllowEmptyStrings = true)] attribute.

    You could create a ViewModel with

    [DisplayName("Event Type")]
    public EventType? EventType { get; set; }
    

    Or just remove it in your new view model

    If you want disable this feature in razorpage project,try

    builder.Services.AddRazorPages().AddMvcOptions(op=>op.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes=true);