Razor-pages form is not hitting the post method - asp.net-core

My post form is not working - the method in C# class is not even executing.
I tried some soutions I found, but still can't handle with that.
My simple view ReportBug.cshtml:
#page
#model Report
<div class="m-3 p-3">
<div class="text-center">
<h4 class="display-4">Report a bug</h4>
</div>
<form method="post">
<div class="row">
#(Html.Kendo()
.TextBoxFor(t => t.Title)
.Placeholder("Title")
.HtmlAttributes(new { style = "width: 25%" })
)
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
My C# class is:
public class ReportBugModel : PageModel
{
public void OnGet()
{
}
public void OnPost()
{
}
public void OnPost(Report report)
{
}
}
as you can see I tried call this method with no parameter and with 1 parameter (Report model).
So summarizing:
I have #addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers in viewImports
I tried tag helpers asp-antiforgery="true"
Before these post method were named OnPostReport(Report report) and I used tag helper asp-page-handler="Report", but also failed.
So far after click Submit button my page is only reloading and I don't have any erros in console.
#Edit
Here is the generated HTML code:
<div class="m-3 p-3">
<div class="text-center">
<h4 class="display-4">Report a bug</h4>
</div>
<form method="post">
<div class="row">
<span class="k-widget k-textbox" style="width: 25%;"><input id="Title" name="Title" style="width: 100%;" value="" data-role="textbox" aria-disabled="false" class="k-input" placeholder="Title" autocomplete="off"></span><script>kendo.syncReady(function(){jQuery("#Title").kendoTextBox({"placeholder":"Title"});});</script>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<input name="__RequestVerificationToken" type="hidden" value="CfDJ8Bbl_ufFcklEjkehOGdz8BtSJK0b5YyLKm-ID2YYYWig_98ZBjFzd9-V_cDDrtBJqiKXJmW7blydpKIKa9qdz9sZZldP3cmya-BVho3uUIbW3_Ob-BVrLmAUi_KHq3eKEAE7nrELLwzebuzXTmnsP6sK2MubiEb3lK3mqOzmVERB2NmXvpI43QmwL-lGUr43Rw">
</form>
</div>

You may misunderstand Razor Pages,please firstly learn the get started document:
For how to fix your issue,you could follow the steps below:
1.Change your Razor Pages ReportBug.cshtml(You must change #model Report to #model ReportBugModel):
#page
#model ReportBugModel //change here...
<div class="m-3 p-3">
<div class="text-center">
<h4 class="display-4">Report a bug</h4>
</div>
<form method="post">
<div class="row">
#(Html.Kendo()
.TextBoxFor(t => t.Report.Title) //also change here...
.Placeholder("Title")
.HtmlAttributes(new { style = "width: 25%" })
)
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
2.Change your Razor Page backend code ReportBug.cshtml.cs:
public class ReportBugModel : PageModel
{
public void OnGet()
{
}
public Report Report { get; set; } //add this...
public void OnPost(Report report)
{
}
}
Result:

Related

Show Post submit popup message in ASP.Net Core Razor page without controller

I have an ASP.Net Core Razor web application without controllers.
I have a form in my cshtml page and on Post/Submit I am calling an external API, which returns a success message or an error message. I want to show this message in my page as a popup.
I tried multiple things but failed. Here is my code.
In my "Index.cshtml"
<div class="col-lg-4 col-md-6 footer-newsletter">
<h4>Our Newsletter</h4>
<p>Subscribe to our news letter</p>
<form action="" method="post">
<input type="email" asp-for="SubscriptionEmail" placeholder="Email Address"/>
<input type="submit" value="Subscribe" asp-page-handler="NewsSubscription" />
</form>
</div>
In my Index.cshtml.cs
[BindProperty]
public string SubscriptionEmail { get; set; }
public string ActionResultMessageText { get; set; }
public string ActionResultErrorMessageText { get; set; }
public async void OnPostNewsSubscription()
{
try
{
this.ActionResultMessageText = string.Empty;
this.ActionResultErrorMessageText = string.Empty;
using (HttpClient _httpClient = _httpClientFactory.CreateClient("PortalBasicHttpClient"))
{
if (!string.IsNullOrEmpty(SubscriptionEmail))
{
HttpRequestMessage _Request = new(HttpMethod.Post, _httpClient.BaseAddress + "Api/SaveSubscriptionEmail/" + SubscriptionEmail);
HttpResponseMessage _Response = await _httpClient.SendAsync(_Request);
if (_Response.IsSuccessStatusCode)
{
this.ActionResultMessageText = _Response.Content.ReadAsStringAsync().Result.ToString();
}
else
{
this.ActionResultMessageText = _Response.Content.ReadAsStringAsync().Result.ToString();
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, ex.Message);
this.ActionResultMessageText = string.Empty;
this.ActionResultErrorMessageText = ex.Message;
}
}
My code behind is working fine, but not sure how to grace fully show this in the razor page using bootstrap.
looking forward for some guidance.
I tried using modal popup, but the text was not updated in the label I used in the modal popup and the pop-up disappeared with in few seconds, even though there was a "ok" button.
I also tried to use the java script method as mentioned in the following link https://www.aspsnippets.com/Articles/ASPNet-Core-Razor-Pages-Display-JavaScript-Alert-Message-Box.aspx
I will be great help if someone can help with a sample code.
Please debug your code and be sure the two properties actually contain the value you want.
The following working demo I just hard coded the two properties value for easy testing in the backend:
Index.cshtml
#page
#model IndexModel
<div class="col-lg-4 col-md-6 footer-newsletter">
<h4>Our Newsletter</h4>
<p>Subscribe to our news letter</p>
<form action="" method="post">
<input type="email" asp-for="SubscriptionEmail" placeholder="Email Address" />
<input type="submit" value="Subscribe" asp-page-handler="NewsSubscription" />
</form>
</div>
#if (Model.ActionResultMessageText == string.Empty)
{
<script type="text/javascript">
window.onload = function () {
alert("#Model.ActionResultErrorMessageText");
};
</script>
}
Index.cshtml.cs
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{
_logger = logger;
}
[BindProperty]
public string SubscriptionEmail { get; set; }
public string ActionResultMessageText { get; set; }
public string ActionResultErrorMessageText { get; set; }
public void OnGet()
{
}
public async void OnPostNewsSubscription()
{
this.ActionResultMessageText = string.Empty;
this.ActionResultErrorMessageText = "error";
}
}
Result:
If you want to use Bootstrap modal popup, change your page like below:
#page
#model IndexModel
<div class="col-lg-4 col-md-6 footer-newsletter">
<h4>Our Newsletter</h4>
<p>Subscribe to our news letter</p>
<form action="" method="post">
<input type="email" asp-for="SubscriptionEmail" placeholder="Email Address" />
<input type="submit" value="Subscribe" asp-page-handler="NewsSubscription" />
</form>
</div>
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Modal title</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
#Model.ActionResultErrorMessageText
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button>
</div>
</div>
</div>
</div>
#if (Model.ActionResultMessageText == string.Empty)
{
<script type="text/javascript">
window.onload = function () {
$("#exampleModal").modal("show")
};
</script>
}
Result:

Setting properties using asp-for in MVC Core App with EF Core?

As I understand it from these docs: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro?view=aspnetcore-3.1 , asp-for is used to transfer values from input elements to backend C# class properties, for example:
<input type="text" id="wsite" name="wsite" maxlength="11" asp-for="WebsiteName">
Along with '#folderName ClassName;' at the top, lets you transfer to this example property:
public string WebsiteName { get; set; }
However, testing this out with console.WriteLine show that the property is still null after the form containing the input has been submitted. Any idea what I'm missing?
Edit: Updated to show my property name and asp-for value match, and to add my controller:
[HttpPost]
public IActionResult Post()
{
DBCRUD.Initialize(_context);
return NoContent();
}
The asp-for tag should match the variable-name.
Try defining your html-form like:
#model Classname
<form asp-action="ActionName" asp-controller="ControllerName" ...>
<input type="text" asp-for="VarName">
and your controller:
public MyReturnVariable ActionName(ClassName class) {
Console.WriteLine(class.VarName);
}
The Tag Helpers is used with Model binding and creating and rendering HTML elements(display the model properties) in the web page.
So, in the Web page (or view page), at the top of the header, we should add the following code to assign the model.
#model MVCSample.Models.BookModel
Then, using the following code to display the properties:
<div class="row">
<div class="col-md-4">
<form asp-action="AddBook">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="ID" class="control-label"></label>
<input asp-for="ID" class="form-control" />
<span asp-validation-for="ID" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="bookName" class="control-label"></label>
<input asp-for="bookName" class="form-control" />
<span asp-validation-for="bookName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Title" class="control-label"></label>
<input asp-for="Title" class="form-control" />
<span asp-validation-for="Title" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
Code in the controller:
[HttpGet]
public IActionResult AddBook()
{
BookModel book = new BookModel()
{
ID = 1001,
bookName = "War and Peace",
Title = "War and Peace"
};
return View(book);
}
Code in the model:
public class BookModel
{
public int ID { get; set; }
public string bookName { get; set; }
public string Title { get; set; }
}
More details information, you could check the Model Binding.

How to collapse/expand Razor components using Blazor syntax?

I'm currently implementing a form to create a new user along with their respective user rights. In this form, I have about 30 different IT systems and if the user account should have the access rights for that specific IT system, I want to provide a panel to the admin where some extra information must be entered regarding that specific IT system. I want to implement this using razor components. What I have so far is the core view for my "new user form" as well as a razor component for the additional information of a specific IT system. By clicking the + button, I want the component to be visible / expand right below the IT system. That's what It looks like so far:
The new user form:
<div class="row">
<div class="col-sm-2 font-weight-bold">GOODWILL PKW/Smart</div>
<div class="col-sm-2">
<label>Add</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Change</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Remove</label>
<input type="checkbox" />
</div>
<div class="col-sm-4">
<button #onclick="#collapseGoodwill">+</button>
</div>
</div>
<ModalGoodwillPKW ></ModalGoodwillPKW>
#code {
public void collapseGoodwill() {
}
}
The component:
<div class="panel panel-default border">
<div class="panel-heading alert-primary">
<h3 class="panel-title">Goodwill PKW/smart</h3>
</div>
<div class="panel-body">
<div class="container-fluid">
<div class="row">
<div class="col-sm-2 font-weight-bold">Profile</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_c" />
<label>Salesman</label>
</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_r" />
<label>Administrator</label>
</div>
</div>
</div>
</div>
</div>
Normally, I would use JQuery in the "collapseGoodwill" method to add a .collapse class to this element. But since I am experimenting with Blazor, I'd like to know if there is a 100% Javascript /JQuery free way of doing this.
Thanks!
Within Blazor, you always follow the pattern:
change data
--> new view rendered
Anytime you want to change the component's UI from outside, you should do it by changing the data (model/state/parameter/context/...).
As for this scenario, you can add a Collapsed field to indicate whether the panel itself is collapsed now:
<div class="panel panel-default border #Collapse">
<div class="panel-heading alert-primary">
<h3 class="panel-title">Goodwill PKW/smart</h3>
</div>
<div class="panel-body">
<div class="container-fluid">
<div class="row">
<div class="col-sm-2 font-weight-bold">Profile</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_c" />
<label>Salesman</label>
</div>
<div class="col-sm-5">
<input type="checkbox" id="CB_r" />
<label>Administrator</label>
</div>
</div>
</div>
</div>
</div>
#code{
[Parameter]
public string Collapse{get;set;}="collapse"; // hide by default
}
And whenever you want to collapse it, just set this parameter to collapse:
<div class="row">
<div class="col-sm-2 font-weight-bold">GOODWILL PKW/Smart</div>
<div class="col-sm-2">
<label>Add</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Change</label>
<input type="checkbox" />
</div>
<div class="col-sm-2">
<label>Remove</label>
<input type="checkbox" />
</div>
<div class="col-sm-4">
<button #onclick="e => this.Collapsed = !this.Collapsed">
#( this.Collapsed ? "+" : "-")
</button>
</div>
</div>
<ModalGoodwillPKW Collapse="#( this.Collapsed ? "collapse": "")" ></ModalGoodwillPKW>
#code {
private bool Collapsed = true;
}
Demo:
[Edit] : we can even refactor the above code to expose less information by changing the field from string to boolean.
The ModalGoodwillPKW.razor:
<div class="panel panel-default border #(Collapsed? "collapse": "" ) ">
<div class="panel-heading alert-primary">
<h3 class="panel-title">Goodwill PKW/smart</h3>
</div>
...
#code{
[Parameter]
public bool Collapsed{get;set;}= true; // hide by default
}
The UserForm.razor:
<div class="row">
...
<div class="col-sm-4">
<button #onclick="e => this.Collapsed = !this.Collapsed">
#( this.Collapsed ? "+" : "-")
</button>
</div>
</div>
<ModalGoodwillPKW Collapsed="#Collapsed" ></ModalGoodwillPKW>
#code {
private bool Collapsed = true;
}
I had a similar issue, I had a dynamic list of sections that I wanted to collapse, and I couldn't get the bootstrap data-toggle approach to work due to Blazor mis-handling of # anchor tags.
I used the component idea:
<div class="row">
#if (Collapsed)
{
<span #onclick="#Toggle" class="oi oi-plus mr-1"/>
}
else
{
<span #onclick="#Toggle" class="oi oi-minus mr-1"/>
}
#Title
</div>
#if(!Collapsed)
{
#ChildContent
}
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool Collapsed { get; set; }
[Parameter]
public string Title { get; set; }
void Toggle()
{
Collapsed = !Collapsed;
}
}
Which I could then use like this:
#foreach (var i in c.Request)
{
<Collapsable Title="#i.SectionName" Collapsed="true">
<ChildContent>
#foreach (var kvp in i.Values)
{
<div class="row">
<div class="col-1"></div>
<div class="col-6 font-weight-bolder">#kvp.Key</div>
<div class="col-5">#kvp.Value</div>
</div>
}
</ChildContent>
</Collapsable>
}
This seems to work well, each section is independently collapsible.
I've not tried it nested though.
Blazor "#Collapse" div with Bootstrap Toggle Button
I took #cjb110 's excellent sample code above and changed it to use a bootstrap badge button as the toggle, which is how I often add more verbose help info to a form field group, by hiding it behind a toggle and using a bootstrap or material info button for if a user wants it.
Component Part
Here's the component part, which you'd probably add to your Blazor solution's Client project's Shared folder as file name Collapsible.razor (note: Blazor component file names are to be capitalized--I think)
<div class="my-1">
<h3>#Title</h3>
#if (Collapsed)
{
<button #onclick="#Toggle" class="badge badge-info mr-2" role="button" >
#ButtonText
</button>
}
else
{
<button #onclick="#Toggle" class="badge badge-info mr-2" role="button" >
#ButtonText
</button>
}
<label>
#LabelText
</label>
</div>
#if(!Collapsed)
{
<div class="card alert alert-info mb-3" role="alert">
#ChildContent
</div>
}
#code {
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public bool Collapsed { get; set; }
//input params coming from from page
[Parameter]
public string Title { get; set; }
[Parameter]
public string ButtonText { get; set; }
[Parameter]
public string LabelText { get; set; }
void Toggle()
{
Collapsed = !Collapsed;
}
}
Template Part
I call this the "template" part. You can change the
Title text,
ButtonText,
I use these info-btn toggles typically in forms, so I added a
<label/> tag with LabelText.
In the <ChildContent/> area, in the component file I set it up as a Bootstrap alert class div, so it doesn't require a <p> tag, but put anything in here you want to show up when the toggle is opened.
<Collapsible
Title=""
ButtonText="Info"
LabelText="Search People & Assign Roles: "
Collapsed="true">
<ChildContent>
Find a person, add their role to the product (i.e.: Estimator, Foreman, Customer)
</ChildContent>
</Collapsible>
I was facing issues with the accordion collapse in my project.
This is how I fixed the bootstrap collapse issue in my Blazor app.
I simply copied these dependencies in the index.html file in Blazor webapp and it worked fine.
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
Reference: https://www.w3schools.com/bootstrap4/tryit.asp?filename=trybs_collapsible&stacked=h
Let us know if this works for anyone else
There are some long and good answers. I thought I'd come in with the most important punchline, though.
You can hide whatever you want based on C# conditional logic. So you will VERY often use something like:
<div #onclick="()=>IsOpened = !IsOpened">Click on me to show the hidden control.</div>
#if (IsOpened){
<MyHiddenControl />
}
#code {
bool IsOpened;
}

How to prevent immediate trigger jQuery validation?

There is some ViewModel:
class MyViewModel
{
[Required(ErrorMessage = "Field {0} is required")]
public string Email { get; set; }
}
I use jquery validation for front-end:
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.16.0/jquery.validate.min.js">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js">
</script>
The fragment of Razor markup:
<form asp-controller="Account" asp-action="Register" role="form">
<div class="form-group">
<div asp-validation-summary="All" class="text-danger"></div>
</div>
<div class="form-group">
<label asp-for="Email"></label>
<input asp-for="Email" class="form-control" aria-describedby="email" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
</form>
The issue is validation is triggered immediately when user get the html page. So one sees error for email field when she inputs nothing yet (Field Email is required). How can I prevent this behavior (triggered on submit)?
There is action:
public IActionResult SomeAction(MyViewModel model = null)
{
return View(model);
}
i.e. controller pass to action null model (value by default). It is the reason of that behavior of jquery validation

Why is the Bind attribute seemingly breaking my model binding of nested objects?

Could someone help me resolve this issue. I'm trying to limit over posting with bind param action but it seems that it doesn't work at all. When I removed the Bind keyword, everything started to work as a charm.
Here is the code sample:
View Model:
public class ProductCreateViewModel
{
public Product Product { get; set; }
public ICollection<IFormFile> Images { get; set; }
}
Action:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Product.Id,Product.CategoryId,Product.Description,Product.Title")] ProductCreateViewModel productVM)
{
if (ModelState.IsValid)
{
_context.Add(productVM.Product);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
ViewData["CategoryId"] = new SelectList(_context.Categories.Include(c => c.Categories).Where(c => c.ParentCategoryId == null), "Id", "Name", productVM.Product.CategoryId);
return View(productVM);
}
View:
#model CatalogWebApp.Models.ProductsViewModels.ProductCreateViewModel
#{
ViewData["Title"] = "Add Product";
ViewData["BigPageTitle"] = "Products";
ViewData["PageBoxTitle"] = "Add New Product";
}
<form asp-action="Create">
<div class="form-horizontal">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Product.CategoryId" class="col-md-2 control-label"></label>
<div class="col-md-10">
<select name="Product.CategoryId" class ="form-control">
#foreach(Category item in (ViewBag.CategoryId as SelectList).Items)
{
<option value="#item.Id">#item.Name</option>
if (item.Categories != null && item.Categories.Count > 0)
{
foreach (var subCat in item.Categories)
{
<option value="#subCat.Id">--#subCat.Name</option>
}
}
}
</select>
</div>
</div>
<div class="form-group">
<label asp-for="Product.Description" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Product.Description" class="form-control" />
<span asp-validation-for="Product.Description" class="text-danger" />
</div>
</div>
<div class="form-group">
<label asp-for="Product.Title" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="Product.Title" class="form-control" />
<span asp-validation-for="Product.Title" class="text-danger" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
</form>
<div>
<a asp-action="Index">Back to List</a>
</div>
#section Scripts {
#{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
Could someone pelase indicate if I have a problem or it is only a known asp.net core issue?
I'm not quite sure why you using Bind for your case.
Just create sepatate ViewModel with only properties you need like ProductCreateStort.
Then use this ViewModel in your controller signature and inherit your main model from it.
This way you won't mess with Bind and limit your params on POST
While I'm fairly new to ASP.NET Core myself (and coming to this question 7 months late), I ran into this same issue. I think the key here is that you have to bind "Product" for it to be considered. Binding "Product.Id" by itself doesn't appear to be good enough. So this should work:
[Bind("Product,Product.Id,Product.CategoryId,Product.Description,Product.Title")]
Of course, Hamid Mosalla's comment is a better option if ALL of your bound properties are on the nested object (which leads to wonder why you need a view model in the first place). In my case, I have a nested object AND a local property, so using the "Prefix" solution wasn't the right thing to do.
Anyway, hope this helps someone.
You need to pass your values as params string[], not as a single string separated by commas:
[Bind("Product.Id","Product.CategoryId","Product.Description","Product.Title")]
See Source