.Net Core 3.1 to .Net 6 breaks model validation

Michael Mastro II 51 Reputation points
2022-03-11T16:00:38.73+00:00

Good morning, I have a MVC view that was working fine in .Net Core 3.1. The view is essentially three sections. An input box for the name, a row of radio buttons, and field of checkboxes. This worked perfectly fine. I made no changes to my ViewModels, View, or Controller. The only change I made was on the project xml file, changing the TargetFramework from netcoreapp3.1 to net6.0. Now when I go to the view and make a change, whether the name or a radio button or a checkbox, I hit the submit button, I get validation returns that is essentially saying each Checkbox and Radio button must be checked. I put a breakpoint in the post controller right at ModelState.IsValid and it is not hitting the break point. Was there a breaking change I missed in the documentation? How do I make this work correctly?

Here is information from controller, views, and ViewModels. Again no changes to any of this, which worked in .Net Core 3.1.
Controller:

// GET: Factions/Edit/5
    public async Task<IActionResult> Edit( int? id )
    {
        //If the FactionID is null then return the error, else get the databse information and display it
        if ( id==null )
        {
            return BadRequest();
        }
        //get the information from the database for the Faction
        FactionViewModel eFaction = await _dts.GetFactionViewModelByIdAsync(id.Value);

        //If the database information does not exist, return not found
        if ( eFaction==null )
        {
            return NotFound();
        }
        //Display the View
        return View( eFaction );
    }

    // POST: Factions/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Edit( FactionViewModel returnedView )
    {
        //If the returnedView model state is valid, save the changes to the database
        if ( ModelState.IsValid )
        {
            await _dts.UpdateFactionAsync(returnedView);
            return RedirectToAction( "Index" );
        }

        //generate the view if something is wrong, use the current ID
        var id = returnedView.FactionNames.FactionID;

        FactionViewModel newView = await _dts.GetFactionViewModelByIdAsync(id);
        //Use the information the user typed
        newView.FactionNames = new FactionNamesListViewModel()
        {
            FactionID = returnedView.FactionNames.FactionID,
            FactionName = returnedView.FactionNames.FactionName
        };
        //Place the selected Reputation Type in the View
        newView.ReputationTypesID = returnedView.ReputationTypesID;
        //Display the View
        return View( newView );
    }

View Models
FactionViewModel:

public FactionViewModel()
    {
        this.FactionNames = new FactionNamesListViewModel();
        this.ReputationTypes = new List<ReputationTypeNamesListViewModel>();
        this.ReputationLevels = new List<ReputationLevelNamesListViewModel>();
    }
    public FactionNamesListViewModel FactionNames { get; set; }
    [Required]
    [Display(Name = "Reputation Type to be associated with Faction")]
    public int ReputationTypesID { get; set; }
    public List<ReputationTypeNamesListViewModel> ReputationTypes { get; set; }
    [Required]
    [Display(Name = "Reputation Levels that apply to the Faction")]
    public List<ReputationLevelNamesListViewModel> ReputationLevels { get; set; }

FactionsNamesListViewModel:

[HiddenInput]
    public int FactionID { get; set; }
    [Required]
    [Display(Name = "Name")]
    public string FactionName { get; set; }
    public bool IsSelected { get; set; }

ReputationTypeNamesListViewModel:

public class ReputationTypeNamesListViewModel
{
    public int ReputationTypeID { get; set; }

    [Display(Name = "Reputation Type Name")]
    public string ReputationTypeName { get; set; }

    public bool IsSelected { get; set; }
}

ReputationLevelNamesListViewModel:

public class ReputationLevelNamesListViewModel
{        
    public int ReputationLevelID { get; set; }
    public string ReputationLevelName { get; set; }
    public bool IsChecked { get; set; }
}

Data Calls:

public async Task<FactionViewModel> GetFactionViewModelByIdAsync(int id)
    {
        FactionViewModel output = new()
        {
            FactionNames = await GetFactionNamesListViewModelsAsync(id),
            ReputationTypes = await _repTypes.GetReputationTypesNamesListViewModelByIdAsync(id),
            ReputationLevels = await _repLevels.GetReputationLevelNamesViewModelByIdAsync(id)
        };
        return output;
    }

public async Task<FactionNamesListViewModel> GetFactionNamesListViewModelsAsync(int id)
    {
        return await _db.Set<FactionNamesListViewModel>().FromSqlRaw(sql: "SELECT Faction_ID AS FactionID, Faction_Name AS FactionName, CAST (1 AS bit) AS IsSelected  FROM Faction WHERE Faction_ID = @p0", parameters: new object[] { id }).FirstOrDefaultAsync();
    }

public async Task<List<ReputationTypeNamesListViewModel>> GetReputationTypesNamesListViewModelByIdAsync(int id)
    {
        List<ReputationTypeNamesListViewModel> output = await _db.Set<ReputationTypeNamesListViewModel>().FromSqlRaw(sql: "SELECT Reputation_Type_ID AS ReputationTypeID, Reputation_Type_Name AS ReputationTypeName, CAST (CASE WHEN  Reputation_Type_ID IN (SELECT ReputationTypeReputation_Type_ID FROM Faction WHERE Faction_ID = @p0) THEN 1 ELSE 0 END AS bit) AS IsSelected FROM ReputationType WHERE ActiveRecord = 1", parameters: new object[] { id }).ToListAsync();
        return output;
    }

public async Task<List<ReputationLevelNamesListViewModel>> GetReputationLevelNamesViewModelByIdAsync(int id)
    {
        List<ReputationLevelNamesListViewModel> output = await _db.Set<ReputationLevelNamesListViewModel>().FromSqlRaw(sql: "GetRepLevelList @p0", parameters: new object[] { id }).ToListAsync();
        return output;
    } 

View:

@{
    ViewData[ "Title" ] = "Edit";
    Layout = "~/Views/Shared/_GameDataLayout.cshtml";
}
@model Lotro.ViewModelClass.Dtl.Models.FactionViewModels.FactionViewModel

<h2>Edit</h2>

<form asp-controller="Factions" asp-action="Edit" method="post">
    <div class="form-horizontal">
        <h4>Factions</h4>
        <hr />
        <div asp-validation-summary="All" class="text-danger"></div>
        <div class="form-group row">
            <label asp-for="@Model.FactionNames.FactionName" class="control-label col-lg-2"></label>
            <div class="col-lg-10">
                <input type="hidden" asp-for="@Model.FactionNames.FactionID" />
                <input asp-for="@Model.FactionNames.FactionName" class="form-control" />
            </div>
        </div>
        <div class="form-group row">
            <label asp-for="@Model.ReputationTypesID" class="control-label col-lg-12" style="text-align:center"></label>
            <div class="col-lg-12 row">
                @for ( var i = 0; i < @Model.ReputationTypes.Count; i++ )
                {
                    <div class="col-lg-2">
                        <input type="hidden" asp-for="@Model.ReputationTypes[i].ReputationTypeID" />
                        <input type="radio" asp-for="@Model.ReputationTypesID" value="@Model.ReputationTypes[i].ReputationTypeID" checked="@Model.ReputationTypes[i].IsSelected" />
                        <b>@Html.DisplayFor( model => model.ReputationTypes[ i ].ReputationTypeName )</b>
                    </div>
                }
            </div>
        </div>
        <div class="form-group row">
            <label asp-for="@Model.ReputationLevels" class="control-label col-lg-12" style="text-align:center"></label>
            <div class="col-lg-12 row">
                @{ int j = 0; }
                @for ( var i = 0; i < Model.ReputationLevels.Count; i++ )
                {
                    <input type="hidden" asp-for="@Model.ReputationLevels[i].ReputationLevelID" />
                    <div class="col-lg-1">
                        <input type="checkbox" asp-for="@Model.ReputationLevels[i].IsChecked" />
                    </div>
                    <div class="col-lg-2">
                        @Html.DisplayFor( model => model.ReputationLevels[ i ].ReputationLevelName )
                    </div>
                    j++;
                    if ( j == 4 )
                    {
                        <div class="col-lg-12"><br /></div>
                        j = 0;
                    }
                    else
                    {
                        continue;
                    }
                }
            </div>
        </div>
        <div class="form-group row">
            <div class="col-lg-offset-2 col-lg-10">
                <input type="submit" value="Save" class="btn btn-secondary" />
            </div>
        </div>
    </div>
    <div>
        <a asp-action="Index">Back to List</a>
    </div>
</form>
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,178 questions
{count} votes

1 answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 56,526 Reputation points
    2022-03-11T16:25:51.65+00:00

    if you change the names in the form, you need to make the same changes in post back model. if you remove a form field, remove the matching property from the post back model or use a binding list on the action (a proper model is a better choice).

    you can also make the model fields nullable so they are optional.