I have a layout (_Layout.cshtml
) that i want to render a view (Feedback.cshtml
) onto as a seperate page alongside the RenderBody()
. The view itself will come off from the right hand side of the layout, using a button to toggle it paning to and from the right. On that view there is a form for feedback. Below is example code for the files mentioned:
Layout.cshtml
<div class="feedback">
@await Html.PartialRender("Feedback", new FeedbackModel())
</div>
<div class="content">
@RenderBody()
</div>
Feedback.cshtml
@model FeedbackModel
<div id="offcanvas formContainer">
<form id="feedbackForm" method="post">
<!-- Form fields -->
<button type="submit">Submit</button>
</form>
</div>
Feedback.cshtml.cs
public class FeedbackModel : PageModel
{
<!-- Properties and fields --!>
public async Task<IActionResult> OnPostAsync()
{
<!-- Transform data, Add data to DB via repository methods --!>
return Page();
}
}
Everytime I click the "Submit" button on the Feedback page, it does nothing. It doesn't call the post method at all. If I append a asp-page-hander="@Model.OnPostAsync()"
on the form or an onclick="@Model.OnPostAsync()"
on the button - then before the layout even fully loads, it fires the OnPostAsync()
method immediately, without me even opening the partial view on the page or clicking the button. Can anyone suggest a solution to this?
I thought this logic could instead be moved to a ViewComponent but I am not sure how I would be able to do OnPostAsync()
when the only supplied method is InvokeAsync()
.
So here's one way. Create a Razor page called Feedback (as you have done) containing the form. Here's the PageModel:
public class FeedbackModel : PageModel
{
[BindProperty]
public string Comments { get; set; }
[BindProperty]
public string Email { get; set; }
public void OnGet()
{
}
public void OnPost()
{
// process the feedback
}
}
And here's the view:
@page
@model WebApplication3.Pages.FeedbackModel
@{
Layout = null;
}
<h3 class="fw-light text-muted">Provide your feedback!</h3>
<form id="feedbackForm" method="post">
<div class="form-group mb-3">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
</div>
<div class="form-group mb-3">
<label asp-for="Comments" class="control-label"></label>
<textarea asp-for="Comments" class="form-control"></textarea>
</div>
<button type="button" class="btn btn-sm btn-success submit" data-bs-dismiss="offcanvas">Submit</button>
</form>
Note that the Layout
is set to null
. We only want the form HTML. Also, the submit button is type="button"
so that the form is NOT submitted when it is clicked. In addition, it is wired up with a data-bs-dismiss
attribute so that it closes the offcanvas when clicked.
Add the offcanvas HTML to the Layout page:
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasRight">
<div class="offcanvas-body"></div>
</div>
Along with a button to open it:
<button type="button" class="btn btn-primary feedback" data-bs-toggle="offcanvas" data-bs-target="#offcanvasRight">Feedback</button>
Finally, add the following script to the Layout page that fetches the HTML for the form when it is clicked and includes it in the offcanvas. It also wires up a click event handler to the non-submit button that posts the content of the form to the OnPost
handler of the Feedback page:
<script>
const feedbackButton = document.querySelector('button.feedback');
const offCanvas = document.querySelector('#offcanvasRight .offcanvas-body');
feedbackButton.addEventListener('click', _=>{
fetch(`/feedback`).then(response => response.text()).then(html => {
offCanvas.innerHTML = html;
const form = offCanvas.querySelector('#feedbackForm');
const submit = form.querySelector('button.submit');
submit.addEventListener('click', _=>{
const formData = new FormData(form);
fetch(`/feedback`, {
method:'post',
body:new URLSearchParams(formData)
});
})
})
})
</script>