Razor Pages PageRemote preventing asp-page-handler from being called

0belix 1 Reputation point
2022-02-21T19:07:36.303+00:00

Hi,

I'm facing an odd issue here.

I have a page where i have setup a PageRemote attribute to validate a field, and on the same page, i have 2 buttons that each call a distinct handler using the asp-page-handler property (1 to update the content, the other to delete it).

If i remove the PageRemote validation, the handlers for both buttons are triggered as expected, but when i leave the PageRemote validation on the page, the buttons specific handlers are not called, although the PageRemote handler is correctly triggered.

Furthermore, when leaving the PageRemote validation on the page, if i force its validation to return false, then afterwards the buttons trigger their respective handler, but if i do not force it, then the behaviour is as described earlier, where the handlers are not triggered.

Can someone explain me if there is a catch on using these two things on the same page, and/or how to overcome this?

Below is a sample of the page code:

public class EditModel : PageModel
{

    private IConfiguration _configuration;  

    [Required(ErrorMessageResourceType = typeof(ErrorMessages),  
        ErrorMessageResourceName = nameof(ErrorMessages.SlugRequired))]  
    [MinLength(3, ErrorMessageResourceType = typeof(ErrorMessages),  
        ErrorMessageResourceName = nameof(ErrorMessages.SlugMinLength))]  
    [MaxLength(128, ErrorMessageResourceType = typeof(ErrorMessages),  
        ErrorMessageResourceName = nameof(ErrorMessages.SlugMaxLength))]  
    [RegularExpression(@"^[0-9a-zA-Z_/-]*$", ErrorMessageResourceType = typeof(ErrorMessages),  
        ErrorMessageResourceName = nameof(ErrorMessages.SlugAllowedCharacters))]  
    [PageRemote(ErrorMessageResourceType = typeof(ErrorMessages),  
        ErrorMessageResourceName = nameof(ErrorMessages.SlugDuplicate),  
        AdditionalFields = "__RequestVerificationToken,GenericContentDTO.GenericContent.GenericContentId",  
        HttpMethod = "post",  
        PageHandler = "CheckSlug")]  
    [Display(Name = "URL Title")]  
    [BindProperty]  
    public string Slug { get; set; }  

    [TempData]  
    public string FormResultMessage { get; set; }  

    [BindProperty]  
    public GenericContentDTO GenericContentDTO { get; set; }  

    public EditModel(IConfiguration configuration)  
    {  
        _configuration = configuration;  
    }  

    public IActionResult OnGet(Guid genericContentId)  
    {  
        if (genericContentId.ToString() == "")  
        {  
            return NotFound();  
        }  

        ...  
		Code to load content  
		...  

        return Page();  

    }  

    public async Task<IActionResult> OnPostUpdate()  
    {  

        if (!ModelState.IsValid)  
        {  
            return Page();  
        }  

        ...  
		Code to update content  
		...  

        return RedirectToPage(new { GenericContentId = GenericContentDTO.GenericContent.GenericContentId });  

    }  

    public IActionResult OnPostDelete()  
    {  

		...  
		Code to delete content  
          

    }  

    public JsonResult OnPostCheckSlug()  
    {  

        var token = HttpContext.Session.GetString("APIAuthorizationToken");  

        CheckSlugDTO CheckSlug = new CheckSlugDTO  
        {  
            Slug = Slug,  
            ContentId = GenericContentDTO.GenericContent.GenericContentId,  
            Exists = true  
        };  

        CheckSlug = APICommunicationHelper.PostData<CheckSlugDTO>(CheckSlug, _configuration["AppConfigs:APIAddress"] + "api/CheckGenericContentSlugExistance", token);  

        return new JsonResult(!CheckSlug.Exists);  
    }  
}  

And of the Razor Code:

@Anonymous "{GenericContentId:Guid}"
@默 MyProject.Pages.Generic_Contents.EditModel
@{
}

<form method="post" id="editForm" name="editForm" enctype="multipart/form-data">

<h3>@TempData["FormResultMessage"]</h3>  

<ul class="nav nav-tabs">  
    <li class="nav-item"><a class="nav-link active" data-toggle="tab" href="#main">Main</a></li>  
    <li class="nav-item"><a class="nav-link" data-toggle="tab" href="#meta">Meta</a></li>  
</ul>  

<div class="tab-content">  

    <div id="main" class="tab-pane active">  
        <br />  

        <div asp-validation-summary="ModelOnly" class="text-danger"></div>  

        @Html.HiddenFor(model => model.GenericContentDTO.GenericContent.GenericContentId)  

        <div class="form-group">  
            <label asp-for="GenericContentDTO.GenericContent.Title" class="control-label"></label>  
            <input asp-for="GenericContentDTO.GenericContent.Title" class="form-control" />  
            <span asp-validation-for="GenericContentDTO.GenericContent.Title" class="text-danger"></span>  
        </div>  

        <div class="form-group">  
            <label asp-for="Slug" class="control-label"></label>  
            <input asp-for="Slug" class="form-control" />  
            <span asp-validation-for="Slug" class="text-danger"></span>  
        </div>  

        <div class="form-group row">  

            <div class="col-4">  
                <label asp-for="GenericContentDTO.MainImage" class="control-label"></label>  
                <input asp-for="GenericContentDTO.MainImage" type="file" class="form-control" />  
            </div>  

        </div>  

        <div class="form-group">  
            <label asp-for="GenericContentDTO.GenericContent.Summary" class="control-label"></label>  
            @Html.TextAreaFor(model => model.GenericContentDTO.GenericContent.Summary, new { @class = "form-control", @rows = 5 })  
            <span asp-validation-for="GenericContentDTO.GenericContent.Summary" class="text-danger"></span>  
        </div>  

        <div class="form-group">  
            <label asp-for="GenericContentDTO.GenericContent.ContentText" class="control-label"></label>  
            @Html.TextAreaFor(model => model.GenericContentDTO.GenericContent.ContentText, new { @class = "form-control editorHtml" })  
        </div>  

        <div class="form-group">  
            <label asp-for="GenericContentDTO.GenericContent.IsActive" class="control-label"></label>  
            @Html.CheckBoxFor(model => model.GenericContentDTO.GenericContent.IsActive)  
        </div>  

    </div>  

    <div id="meta" class="tab-pane fade">  
        <div class="form-group">  
            <label asp-for="GenericContentDTO.GenericContent.MetaDescription" class="control-label"></label>  
            @Html.TextAreaFor(model => model.GenericContentDTO.GenericContent.MetaDescription, new { @class = "form-control", @rows = 5 })  
            <span asp-validation-for="GenericContentDTO.GenericContent.MetaDescription" class="text-danger"></span>  
        </div>  

        <div class="form-group">  
            <label asp-for="GenericContentDTO.GenericContent.MetaKeywords" class="control-label"></label>  
            @Html.TextAreaFor(model => model.GenericContentDTO.GenericContent.MetaKeywords, new { @class = "form-control", @rows = 5 })  
            <span asp-validation-for="GenericContentDTO.GenericContent.MetaKeywords" class="text-danger"></span>  
        </div>  
    </div>  


    <div class="form-group text-right">  
        <a asp-area="" asp-page="/Generic-Contents" class="btn btn-secondary">Cancel</a>  
        <button type="submit" class="btn btn-danger" asp-page-handler="Delete">Delete Content</button>  
        <button type="submit" class="btn btn-primary" asp-page-handler="Update">Update Content</button>  
    </div>  

</div>  

</form>

@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

Thanks

ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,504 questions
0 comments No comments
{count} votes

2 answers

Sort by: Most helpful
  1. Zhi Lv - MSFT 32,146 Reputation points Microsoft Vendor
    2022-02-22T03:07:06.01+00:00

    Hi @0belix ,

    First, to the delete button, it should not use the submit type button, because if using this type of button, once click the button, it will submit the form and do the model validation. So, try to modify the button to a hyperlink button, code like this:

     <a asp-page="/Edit" class="btn btn-danger" asp-page-handler="Delete" asp-route-id="@Model.GenericContentDTO.GenericContent.GenericContentId">Delete Content</a>  
    

    It will send a http get request and transfer the GenericContentId (assume this is the primary key) to the Delete handler, so we also need to change the Delete hander method as below:

        public IActionResult OnGetDelete(int genericContentId)  
        {  
            // find the GenericContent based on the GenericContentId  
            GenericContentDTO = _dataRepo.FindGenericContentByID(genericContentId);  
    
            //delete item from the database   
    
            return Page();  
        }  
    

    To the update button, you can use the Submit type button to submit the form and validate the entered value.

    Second, to the PageRemote validation, since there has an AdditionalFields ("GenericContentDTO.GenericContent.GenericContentId") to validate, the OnPostCheckSlug should have two parameters, like this:
    Note: in my sample, I changed the GenericContentId from Guid Type to Int type, you can change it to yours:

        public JsonResult OnPostCheckSlug(string slug, int GenericContentId)  
        {  
            //validate the entered slug.  
            var sluglist = new List<string>() { "Sslug1", "Sslug2" };  
    
            var isexist = !sluglist.Contains(slug);  
            return new JsonResult(isexist);  
        }  
    

    The result is like this:

    176651-1.gif

    You can view the source code from these attachments: 176558-edit-html.txt 176625-edit-html-cs.txt 176539-mode.txt

    Besides, since there has other validation such as required, MinLength or MaxLength, when you enter the slug value, if this value not achieve these rules, it will also prevent the update button.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    Best regards,
    Dillion


  2. 0belix 1 Reputation point
    2022-02-22T11:49:37.263+00:00

    Hi ZhiLv-MSFT,

    First, let me thank you for the reply.

    However, even after implementing the changes you suggested (which did not address the real issue here), the problem persists. The issue was not the validation being correctly fired, or the model validation being passed to the Delete handler via Post, since as i explained earlier, i can have any of those to correctly process the request when they are indeed fired.

    The real issue is that if the user loads the page, and inside the Slug field is a valid value (and i do not change it), even if i change any of the other values on the other fields, and then press the Update button, if will not fire its handler, instead firing a default OnPost handler (i know it fires the OnPost since if i implement a default OnPost on the page and set a break point on it, it will get fired at that point).

    But if upon loading the page, i change the value on the slug input to an invalid value, the validation is fired correctly, stating that the value is not acceptable, then changing it back to an acceptable value, and pressing the Update button, it will then fire the correct handler.

    So, in sum, the issue persists, regardless of the suggested corrections... Any other ideas that might overcome this?


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.