I'm new to Razor pages and having trouble with model binding back to the view.
I'm using VS2019 version 16.0.4.
This is my PageModel:
public class IndexModel : PageModel
{
[BindProperty]
public int PageIndex { get; set; }
public IActionResult OnPost()
{
PageIndex++;
return Page();
}
}
And my View:
#page
#model IndexModel
<form method="post">
<div class="form-group">
<label asp-for="PageIndex"></label>
<input asp-for="PageIndex" class="form-control" />
<span class="text-danger" asp-validation-for="PageIndex"></span>
<button type="submit" class="btn btn-primary">Increment</button>
</div>
</form>
I would expect the value displayed in the input control to be incremented on each click - but it remains at zero. The binding into the controller seems to work ok. If I enter a value of "5" and click the button then a breakpoint shows me that a value of 5 is received before being incremented to 6. However, the incremented value does not get reflected back to the view.
Where did I go wrong?
Model binding takes values from the HTTP request and binds them to handler method parameters or PageModel properties. It is not two-way, and does not then bind those values back to the controls where the values originated.
You need to explicitly set the value attribute of the input to see the behaviour that you expect:
<input asp-for="PageIndex" value="#Model.PageIndex" />
Related
So I have this issue that I am not unable to solve the way I think it's supposed to be solved.
I have an ASP.NET Core 2.1 Razor pages project. The code is pasted below and my problem is the following:
On the index page, I have a search form. The city name I enter in the search form gets used in the SearchResults OnPost method.
The OnPost redirects to OnGet which retrieves some results from the database based on the city passed in from the search form. From my understanding, TempData should be able to retain the value for the city passed in from the form, however, whenever I try to read TempData["CityFromForm"] in the OnGet method, the TempData dictionary is empty, even though that in the OnPost method I used the TempData.Keep method.
My current solution for this is using in memory cache to store the city value and pass it to the method that fetches the data from the database, but I would like to know why the TempData approach is not working.
On the index page on that project, there is a search from in which I enter a city for which I want to search the data, like so:
#model SearchDataViewModel
<form asp-page="/Search/SearchResults" method="post" class="col s6">
<div class="row">
<div class="input-field col s12">
<input placeholder="Please enter a city" type="text" name="City" class="validate autocomplete" id="autocomplete-input" autocomplete="off" />
<label for="City">City</label>
</div>
</div>
<div class="row">
<div class="input-field col s6">
<input id="StartDate" name="StartDate" type="text" class="datepicker datepicker-calendar-container">
<label for="StartDate">Start date</label>
</div>
<div class="input-field col s6">
<input id="EndDate" name="EndDate" class="datepicker datepicker-calendar-container" />
<label for="EndDate">End date</label>
</div>
</div>
<input type="submit" hidden />
</form>
What matters in that form is the city. That form gets sent to the SearchResults razor page.
SearchResults.cshtml.cs
public IActionResult OnPost()
{
// Cache search form values to persist between post-redirect-get.
var cacheEntry = Request.Form["City"];
_cache.Set<string>("City", cacheEntry);
TempData["CityFromFrom"] = Request.Form["City"].ToString();
TempData.Keep("CityFromForm");
return RedirectToPage();
}
// TODO: Find a better way to persist data between onPost and OnGet
public async Task OnGet(string city)
{
City = _cache.Get<string>("City");
var temp = TempData["CityFromForm"];
// Here I'd like to pass the TempData["CityFromForm"] but it's null.
await GetSearchResults(City); // this method just gets data from the database
}
TempData keys are prefixed by "TempDataProperty-". So if you have a key named "City", you access it via TempData["TempDataProperty-City"].
See https://www.learnrazorpages.com/razor-pages/tempdata
You also have a typo in the line where you assign the tempdata value: TempData["CityFromFrom"] should be TempData["CityFromForm"], I suspect.
So here is what I came up with, basically I get a city string from the search form. In the OnPost method I redirect to page where I add a route value which OnGet method can use.
In SearchResults.cshtml I added a #page "{city?}"
The url ends up looking like: https://localhost:44302/Search/SearchResults?searchCity={city}
In SearchResults.cshtml.cs
public async Task OnGet()
{
City = HttpContext.Request.Query["searchCity"];
PetGuardians = await GetSearchResults(City);
}
public IActionResult OnPost(string city)
{
return RedirectToPage(new { searchCity = city });
}
In my current application by using init binder-StringTrimmerEditor we are nullifying all the values which are empty from the view/templates. But now I want to remove one field(movielist) from being nullified as this particular field when I edit the form i.e., remove all the values in the movie-list and click save button controller is getting null value instated of empty string. I want it to be as empty String instead of null value.
How do I exclude the movielist from being nullified.
<form action="#" th:object="${CustomerForm}" th:action="#{customer/save}" method="post">
<input type="hidden" th:field="*{id}"/>
<textarea th:field="*{movieList}"></textarea>
<div class="modal-footer">
<input class="btn-submit" type="submit" value="Save"/>
</div>
</form>
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String.class, new StringTrimmerEditor(emptyAsnull:true));
}
You can look at this question
. You might have to set the allowed fields value for your databinder which will also solve a potential security concern.
In a standard MVC app, scaffolding controller with views gives a Delete view with this Razor form:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
<div class="form-actions no-color">
<input type="submit" value="Delete" class="btn btn-default" /> |
#Html.ActionLink("Back to List", "Index")
</div>
}
Nowhere in the Delete view is any id field, hidden or not.
Then the controller for that view has this action:
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(long id)
{
....
}
Where does this action get its id parameter value from? Is it somehow extracted from the only form value posted, the anti-forgery token __RequestVerificationToken, during some sort of model binding?
The id is passed as parameter when you redirected to the Delete action:
[HttpGet]
public ActionResult Delete(long id)
{
return View();
}
and since it is part of the original url (look at your browser address bar at the moment the Delete view is displayed) it will be preserved by the Html.BeginForm() helper - now look at the generated HTML markup and you will see this:
<form action="/somecontroller/delete/123" method="post">
...
</form>
That's where the id is coming from - the action of the generated form.
Because you have a GET method with a signature
public ActionResult Delete(long id)
and you using the default route (or at least a route definition containing /{id})
url: "{controller}/{action}/{id}"
When you navigate to that method, say using /yourController/Delete/10, the value of id is 10, and that is added to the action attribute of the <form> tag generated by your Html.BeginForm() method. When you submit the form, the value of the id parameter is bound for the route value in the forms action attribute (the DefaultModelBinder reads values from the form collection (any inputs you might have) as well as route values and query strings (among others)
I have a form in ASP.Net and razor.
I need to have two ways of submitting said form: one that goes through the Edit action, and another that goes through the Validate action.
How should I go about doing this?
I don't mind using JavaScript for this.
EDIT:
Using the custom attribute I get this error.
The current request for action 'Resultados' on controller type 'InspecoesController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Validar(System.Collections.Generic.ICollection1[Waveform.IEP.Intus.Server.Web.ViewModels.ResultadoViewModel]) on type Waveform.IEP.Intus.Server.Web.Controllers.InspecoesController
System.Web.Mvc.ActionResult Resultados(System.Collections.Generic.ICollection1[Waveform.IEP.Intus.Server.Web.ViewModels.ResultadoViewModel]) on type Waveform.IEP.Intus.Server.Web.Controllers.InspecoesController
That's what we have in our applications:
Attribute
public class HttpParamActionAttribute : ActionNameSelectorAttribute
{
public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo)
{
if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase))
return true;
var request = controllerContext.RequestContext.HttpContext.Request;
return request[methodInfo.Name] != null;
}
}
Actions decorated with it:
[HttpParamAction]
public ActionResult Save(MyModel model)
{
// ...
}
[HttpParamAction]
public ActionResult Publish(MyModel model)
{
// ...
}
HTML/Razor
#using (#Html.BeginForm())
{
<!-- form content here -->
<input type="submit" name="Save" value="Save" />
<input type="submit" name="Publish" value="Publish" />
}
name attribute of submit button should match action/method name
This way you do not have to hard-code urls in javascript
You can do it with jquery, just put two methods to submit for to diffrent urls, for example with this form:
<form id="myForm">
<%-- form data inputs here ---%>
<button id="edit">Edit</button>
<button id="validate">Validate</button>
</form>
you can use this script (make sure it is located in the View, in order to use the Url.Action attribute):
<script type="text/javascript">
$("#edit").click(function() {
var form = $("form#myForm");
form.attr("action", "#Url.Action("Edit","MyController")");
form.submit();
});
$("#validate").click(function() {
var form = $("form#myForm");
form.attr("action", "#Url.Action("Validate","MyController")");
form.submit();
});
</script>
If you are working in asp.net with razor, and you want to control multiple submit button event.then this answer will guide you. Lets for example we have two button, one button will redirect us to "PageA.cshtml" and other will redirect us to "PageB.cshtml".
#{
if (IsPost)
{
if(Request["btn"].Equals("button_A"))
{
Response.Redirect("PageA.cshtml");
}
if(Request["btn"].Equals("button_B"))
{
Response.Redirect("PageB.cshtml");
}
}
}
<form method="post">
<input type="submit" value="button_A" name="btn"/>;
<input type="submit" value="button_B" name="btn"/>;
</form>
Here is a good eplanation:
ASP.NET MVC – Multiple buttons in the same form
In 2 words:
you may analize value of submitted button in yout action
or
make separate actions with your version of ActionMethodSelectorAttribute (which I personaly prefer and suggest).
With HTML5 you can use button[formaction]:
<form action="Edit">
<button type="submit">Submit</button> <!-- Will post to default action "Edit" -->
<button type="submit" formaction="Validate">Validate</button> <!-- Will override default action and post to "Validate -->
</form>
<input type="submit" value="Create" name="button"/>
<input type="submit" value="Reset" name="button" />
write the following code in Controler.
[HttpPost]
public ActionResult Login(string button)
{
switch (button)
{
case "Create":
return RedirectToAction("Deshboard", "Home");
break;
case "Reset":
return RedirectToAction("Login", "Home");
break;
}
return View();
}
We can have this in 2 ways,
Either have 2 form submissions within the same View and having 2 Action methods at the controller but you will need to have the required fields to be submitted with the form to be placed within
ex is given here with code Multiple forms in view asp.net mvc with multiple submit buttons
Or
Have 2 or multiple submit buttons say btnSubmit1 and btnSubmit2 and check on the Action method which button was clicked using the code
if (Request.Form["btnSubmit1"] != null)
{
//
}
if (Request.Form["btnSubmit2"] != null)
{
//
}
I've a MVC application, whose SharedLayout view(Master Page) gives user capability to search. They could search their order by Order No or By Bill no. So there are two option buttons the Shared View along with the textbox. Code is somewhat like this
#using (Html.BeginForm("Track", "Tracking", FormMethod.Post))
{
<div style="text-align: center">
<textarea cols="20" id="txtNo" name="txtOrderNo" rows="2" ></textarea>
</div>
<div style="text-align: center">
<input type="radio" name="optOrderNo" checked="checked" value="tracking" />Order No <input type="radio" name="optRefNo" value="tracking" />Ref No
</div>
<div style="text-align: center">
<input type="submit" value="Track" />
</div>
}
So it'll go to TrackingController and Track Method in it and return the view. It works fine for a single search as a View is associated with a controller's methods. It works fine but how could i conditionally return the other view based on the radio button selection.
What i come up with is this
[HttpPost]
public ActionResult Track(FormCollection form)
{
string refNo = null;
if (form["optRefNo"] == null)
{
string OrderNo = form["txtOrderNo"];
var manager = new TrackingManager();
var a = manager.ConsignmentTracking(OrderNo);
var model = new TrackingModel();
if (OrderNo != null)
model.SetModelForConsNo(a, consNo);
return View(model);
}
refNo = form["txtConsNo"];
return TrackByRef(refNo);
}
public ActionResult TrackByRef(string refNo)
{
//what ever i want to do with reference no
return View();
}
Kindly guide.
Thanks
View has an overload where the first parameter is a string. This is the name (or path) to the view you want to use, rather than the default (which is a view that matches the action's name).
public ActionResult TrackByRef(string refNo)
{
//what ever i want to do with reference no
return View("Track");
// or, if you want to supply a model to Track:
// return View("Track", myModel);
}