locked
Razor Page not returning correctly when redisplaying page for errors. RRS feed

  • Question

  • User1374623307 posted

    Good evening,

       I am having a minor issue when trying to return to the Razor Page, if there are errors coming back from the server.  This is an Edit of data, so some of the information and the errors display correctly, while the labels next to the checkboxes do not display at all.

    So it renders fine in the Get, but in the Post it will display the checkboxes, and checked checkboxes on return from an error. 

    Here is the Edit.cshtml

    @page "{id}"
    @model EditModel
    @{
        ViewData["Title"] = "Role Management - Edit Roles";
    }
    
    <form class="col-md-12" asp-route-returnUrl="@Model.ReturnUrl" method="post">
        <input hidden asp-for="ApplicationRoles.Id" />
        <div class="col-md-6">
            <h4>Edit a role.</h4>
            <hr />
            <div asp-validation-summary="All" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Input.RoleName"></label>
                <input asp-for="Input.RoleName" class="form-control" />
                <span asp-validation-for="Input.RoleName" class="text-danger"></span>
            </div>
        </div>
        <div class="form-group row">
            @{ int j = 0;}
            @for (int i = 0; i < Model.DatabasesList.Count; i++)
            {
                <input type="hidden" asp-for="@Model.DatabasesList[i].DatabaseId" value="@Model.DatabasesList[i].DatabaseId" />
                <input asp-for="@Model.DatabasesList[i].IsChecked" type="checkbox" /> <label>&nbsp; @Model.DatabasesList[i].DatabaseName &ensp;</label>        
                j++;
                if (j == 3)
                {
                    <div style="margin-bottom:20px" />
                    j = 0;
                }
            }
        </div>
        <div class="form-group row">
            <div class="col-md-6">
                <button type="submit" class="btn btn-primary">Edit</button>
            </div>
            <div class="col-md-6">
                <a asp-page="RoleManagement" class="btn btn-danger">Cancel</a>
            </div>
        </div>
    </form>
    
    @section Scripts {
        <partial name="_ValidationScriptsPartial" />
    }

    Here is the Edit.cshtml.cs

    public class EditModel : PageModel
        {
            private readonly APIHelper _apiHelper;
    
            public EditModel(APIHelper apiHelper)
            {
                _apiHelper = apiHelper;
            }
    
            [BindProperty]
            public ApplicationRoles ApplicationRoles { get; set; }
    
            [BindProperty]
            public InputModel Input { get; set; }
    
            public string ReturnUrl { get; set; }
    
            public IList<ApplicationDatabases> Databases { get; set; }
    
            public DatabaseInputModel DatabaseInput { get; set; }
    
            [BindProperty]
            public IList<DatabaseInputModel> DatabasesList { get; set; } = new List<DatabaseInputModel>();
    
            public class InputModel
            {
                [Required]
                [Display(Name = "Role Name")]
                [StringLength(256, ErrorMessage = "The {0} must be at least {2} and no more than {1} characters long.", MinimumLength = 2)]
                public string RoleName { get; set; }
    
                public IList<DatabaseInputModel> DatabasesList { get; set; }
            }
            public class DatabaseInputModel
            {
                public int DatabaseId { get; set; }
                public string DatabaseName { get; set; }
                public bool IsChecked { get; set; }
            }
    
            public async Task<PageResult> OnGetAsync(Guid id, string returnUrl = null)
            {
                using (HttpResponseMessage response = await _apiHelper.ApiClient.GetAsync($"/api/Roles/EditRole/{id}"))
                {
                    if (response.IsSuccessStatusCode == false)
                    {
                        throw new Exception(response.ReasonPhrase);
                    }
                    else
                    {
                        var resultString = await response.Content.ReadAsStringAsync();
                        var result = JsonSerializer.Deserialize<EditRoleGetResponseModel>(resultString, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });                    
                        ReturnUrl = result.ReturnUrl;
                        Databases = result.Databases.ToList();      // A list of all the databases on the server              
                        if (result.Role != null)
                        {
                            ApplicationRoles = result.Role;
    
                            foreach (var item in Databases)
                            {
                                DatabasesList.Add(new DatabaseInputModel { DatabaseId = item.Id, DatabaseName = item.DatabaseName });
                            }
                            foreach (var item in result.Role.ApplicationRoleDatabases)
                            {  //Check all the databases that are associated with the role
                                DatabasesList.Select(x =>
                                {
                                    if (x.DatabaseId == item.DatabaseId)
                                    {
                                        x.IsChecked = true;
                                    }
                                    return x;
                                }).ToList();
                            }
                        }
                    }
                }
                Input = new InputModel { RoleName = ApplicationRoles.Name };
                return Page();
            }
    
            public async Task<ActionResult> OnPostAsync(ApplicationRoles role, string returnUrl = null)
            {
                if (ModelState.IsValid)
                {                
                    var resultErrors = new List<KeyValuePair<string, string>>();
                    bool confirmedEdit;
                    IList<int> databases = new List<int>();
                    for (int i = 0; i < DatabasesList.Count; i++)
                    {
                        if (DatabasesList[i].IsChecked == true)
                        {
                            databases.Add(DatabasesList[i].DatabaseId);
                        }
                    }
                    role.Name = Input.RoleName;
    
                    var dataToSend = new EditRolePostDataModel
                    {
                        ApplicationRoles = role,
                        Databases = databases,
                        ReturnUrl = returnUrl
                    };
                    var data = new StringContent(JsonSerializer.Serialize(dataToSend), Encoding.UTF8, "application/json");
                    using (HttpResponseMessage response = await _apiHelper.ApiClient.PutAsync("/api/Roles/EditRole", data))
                    {
                        if (response.IsSuccessStatusCode == false)
                        {
                            throw new Exception(response.ReasonPhrase);
                        }
                        else
                        {
                            var resultString = await response.Content.ReadAsStringAsync();
                            var result = JsonSerializer.Deserialize<EditRolePostResponseModel>(resultString, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                            role = result.Role;
                            ReturnUrl = result.ReturnUrl;
                            Databases = result.Databases.ToList();  // This comes back with all the databases on the server
                            confirmedEdit = result.ConfirmedEdit;
                            resultErrors = result.Errors.ToList();
    
                        }
                        if (confirmedEdit)
                        {
                            return RedirectToPage("RoleManagement", new { returnUrl = returnUrl });
                        }
                    }
                    if (resultErrors.Count > 0)
                    {
                        foreach (var error in resultErrors)
                        {
                            ModelState.AddModelError(string.Empty, error.Value);
                        }
                    }                
                }
                // If we got this far, something failed, redisplay form
                return Page();  // This is not displaying correctly
            }
        }

    Now in trying to fix this I did try to reload the databases with the code I have in the OnGet section with an else after the confirmedEdit.  This did not work as it displayed the checkboxes with no labels, then the checkboxes with labels underneath.  Each errored try would add more and more sets of checkboxes with no labels.  So the modified code for the OnPost looked as such:

    public async Task<ActionResult> OnPostAsync(ApplicationRoles role, string returnUrl = null)
            {
                if (ModelState.IsValid)
                {                
                    var resultErrors = new List<KeyValuePair<string, string>>();
                    bool confirmedEdit;
                    IList<int> databases = new List<int>();
                    for (int i = 0; i < DatabasesList.Count; i++)
                    {
                        if (DatabasesList[i].IsChecked == true)
                        {
                            databases.Add(DatabasesList[i].DatabaseId);
                        }
                    }
                    role.Name = Input.RoleName;
    
                    var dataToSend = new EditRolePostDataModel
                    {
                        ApplicationRoles = role,
                        Databases = databases,
                        ReturnUrl = returnUrl
                    };
                    var data = new StringContent(JsonSerializer.Serialize(dataToSend), Encoding.UTF8, "application/json");
                    using (HttpResponseMessage response = await _apiHelper.ApiClient.PutAsync("/api/Roles/EditRole", data))
                    {
                        if (response.IsSuccessStatusCode == false)
                        {
                            throw new Exception(response.ReasonPhrase);
                        }
                        else
                        {
                            var resultString = await response.Content.ReadAsStringAsync();
                            var result = JsonSerializer.Deserialize<EditRolePostResponseModel>(resultString, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                            role = result.Role;
                            ReturnUrl = result.ReturnUrl;
                            Databases = result.Databases.ToList();
                            confirmedEdit = result.ConfirmedEdit;
                            resultErrors = result.Errors.ToList();
    
                        }
                        if (confirmedEdit)
                        {
                            return RedirectToPage("RoleManagement", new { returnUrl = returnUrl });
                        }
                        else
                        {
                            foreach (var item in Databases)
                            {
                                DatabasesList.Add(new DatabaseInputModel { DatabaseId = item.Id, DatabaseName = item.DatabaseName });
                            }
                            foreach (var item in role.ApplicationRoleDatabases)
                            {
                                DatabasesList.Select(x =>
                                {
                                    if (x.DatabaseId == item.DatabaseId)
                                    {
                                        x.IsChecked = true;
                                    }
                                    return x;
                                }).ToList();
                            }
                        }
                    }
                    if (resultErrors.Count > 0)
                    {
                        foreach (var error in resultErrors)
                        {
                            ModelState.AddModelError(string.Empty, error.Value);
                        }
                    }                
                }
                // If we got this far, something failed, redisplay form
                return Page();  // This is not displaying correctly
            }

    I removed that because it definitely did not give what I was looking for.  What am I missing in my OnPost to get the page to redisplay correctly when there are errors that are not handled by client side validation?

    Thursday, January 14, 2021 10:58 PM

Answers

  • User-821857111 posted

    This is a common problem. If a form needs data to populate dropdownlists, checkbox groups etc when it is generated in the OnGet method, that data also needs to be generated in the OnPost if you want to redisplay the form in the event of validation errors. In your code, you simply return Page() in the event of validation errors. You need to get the data before return Page():

    public async Task OnPost()
    {
        await GetFormData();
    }
    
    public async Task<IActionResult> OnPostAsync()
    {
        if (ModelState.IsValid)
        {
            // process the form
            return RedirectToPage(...);
        }
        else
        {
            await GetFormData();
            return Page();
        }
    }
    
    private async Task GetFormData()
    {
        // get the data the form controls need from the database
    }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, January 15, 2021 8:11 AM
  • User1312693872 posted

    Hi,MikeRM2

    Each errored try would add more and more sets of checkboxes with no labels.

    This is because the return Page() in the post will not generate the DatabasesList in Get method, so you can try to copy the part of code

    which is used to generate the value into OnPostAsync, then the value will still be showed after post denied. you can also write a new method

    to generate the databaseList, it will looks cleaner.

    public JsonResult getname()
            {
    //The way to get the DatabasesList's database name ... return new JsonResult(...); } public async Task<PageResult> OnGetAsync(Guid id, string returnUrl = null) { getname(); .... return Page(); } public async Task<ActionResult> OnPostAsync(ApplicationRoles role, string returnUrl = null) { getname(); .... return Page(); }

    Result:

    Best Regards,

    Jerry Cai

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, January 15, 2021 9:01 AM

All replies

  • User-821857111 posted

    This is a common problem. If a form needs data to populate dropdownlists, checkbox groups etc when it is generated in the OnGet method, that data also needs to be generated in the OnPost if you want to redisplay the form in the event of validation errors. In your code, you simply return Page() in the event of validation errors. You need to get the data before return Page():

    public async Task OnPost()
    {
        await GetFormData();
    }
    
    public async Task<IActionResult> OnPostAsync()
    {
        if (ModelState.IsValid)
        {
            // process the form
            return RedirectToPage(...);
        }
        else
        {
            await GetFormData();
            return Page();
        }
    }
    
    private async Task GetFormData()
    {
        // get the data the form controls need from the database
    }

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, January 15, 2021 8:11 AM
  • User1312693872 posted

    Hi,MikeRM2

    Each errored try would add more and more sets of checkboxes with no labels.

    This is because the return Page() in the post will not generate the DatabasesList in Get method, so you can try to copy the part of code

    which is used to generate the value into OnPostAsync, then the value will still be showed after post denied. you can also write a new method

    to generate the databaseList, it will looks cleaner.

    public JsonResult getname()
            {
    //The way to get the DatabasesList's database name ... return new JsonResult(...); } public async Task<PageResult> OnGetAsync(Guid id, string returnUrl = null) { getname(); .... return Page(); } public async Task<ActionResult> OnPostAsync(ApplicationRoles role, string returnUrl = null) { getname(); .... return Page(); }

    Result:

    Best Regards,

    Jerry Cai

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Friday, January 15, 2021 9:01 AM
  • User1374623307 posted

    Thank You.  I kind of knew I was on the right track with repopulating the list.  Though I see my mistake was doing:

    foreach (var item in Databases)
                            {
                                DatabasesList.Add(new DatabaseInputModel { DatabaseId = item.Id, DatabaseName = item.DatabaseName });
                            }

    So now I put the following in front of my return Page() and it works perfectly fine.

    foreach (var item in Databases)
                {
                    DatabasesList.Select(x =>
                    {
                        if (x.DatabaseId == item.Id)
                        {
                            x.DatabaseName = item.DatabaseName;
                        }
                        return x;
                    }).ToList();
                }
                
                return Page();

    Friday, January 15, 2021 1:20 PM