why post method doesn't refresh the page in the action method? - c#

I know the post action method below is anti-pattern, but still I assume to see a new page with Name being set to null. But when I click the submit button, the page is not reloaded and I still see the old name displayed, is this a browser thing or asp.net core framework thing?
public class HomeController : Controller
{
private IRepository repository;
public HomeController(IRepository repo)
{
repository = repo;
}
// ...
public IActionResult Create() // create a Employer that has a name in the browser
{
return View();
}
[HttpPost]
public IActionResult Create(Employee model)
{
model.Name = ""; // <----------------set it to null so I expect to see the Name being empty in the reponse
return View(model);
}
}
// view file, Create.cshtml:
#model Employee
#{
ViewData["Title"] = "Create Employee";
}
<h2>Create Employee</h2>
<form asp-action="Create" method="post">
<div class="form-group">
<label asp-for="Id"></label>
<input asp-for="Id" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Name"></label>
<input asp-for="Name" class="form-control" />
</div>
<div class="form-group">
<label asp-for="DOB"></label>
<input asp-for="DOB" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Role"></label>
<select asp-for="Role" class="form-control" asp-items="#new SelectList(Enum.GetNames(typeof(Role)))"></select>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Firstly, a get method to invoke the Create() action method and fill some data:
After clicking submit button, the page still shows the original name where I exepct to see a empty name since I modify it in the post action method
so why the name still exists after the second request?

So why the name still exists after the second request?
Well, altough, you have rebind your model property value. However, your current model state still remain unchanged. Therefore, you are getting the older value even after the form resubmission. To overcome this issue, please try following code snippet:
Solution:
You have clear your current model state by using ModelState.Clear(); method before assigning the new value.
So, your create method should be as bellow:
[HttpPost]
public IActionResult Create(Employee model)
{
ModelState.Clear();
model.Name = "";
return View(model);
}
Note: Rest of the code will remain unchanged.
Output:
ModelState.Clear(); wwill resolve your issue completely.

Related

ASP.NET Core - Passing Form data to Controller, then redirect

I currently am trying to build a "Index" form which has two submit boxes, and then when you hit submit, it adds the two numbers together and then displays the result.
I tried this without a Model (Using ViewData) and managed to get it working, but I'd like it to work with a model. I have attempted this a magnitude of ways but I just cannot see what I am missing.
Journey Class - Two string values and a value (this will be int once I get it working)
public class JourneyCalculator
{
public string PostCodeOne { get; set; }
public string PostCodeTwo { get; set; }
public string DistanceValue { get; set; }
}
Then my controller which has two actions, one standard Index form and one that is supposed to Calculate the mileage. With this I've tried the following:
Passing the Model as below
Trying to pass string values from the form (This just fails)
Just simply returning View(model)
public class MapsController : Controller
{
public IActionResult Index(JourneyCalculator journeyCalculator)
{
return View(journeyCalculator);
}
[HttpPost]
public IActionResult CalculateMileage(JourneyCalculator model)
{
model.DistanceValue = model.PostCodeOne + model.PostCodeTwo;
return RedirectToAction("Index",model);
}
}
My form which I would hope passes PostCodeOne and PostCodeTwo to the "CalculateMileage" action. And then we update that value and then the Model.DistanceValue displays
<form>
<div class="form-row">
<div class="form-group col-md-6">
<label>Postcode One</label>
<input type="text" class="form-control" asp-for="PostCodeOne" placeholder="Postcode One">
</div>
<div class="form-group col-md-6">
<label>Postcode Two</label>
<input type="text" class="form-control" asp-for="PostCodeTwo" placeholder="Postcode Two">
</div>
</div>
<button type="submit" class="btn btn-primary" asp-controller="Maps" asp-action="CalculateMileage">Calculate Mileage</button>
</form>
#if(Model != null)
{
<h2>#Model.DistanceValue</h2>
}
I feel I am missing something really obvious, so any help would be fantastic!
When I hit "Submit" the URL updates to https://localhost:44301/Maps/CalculateMileage?PostCodeOne=123&PostCodeTwo=123 or https://localhost:44301/Maps/Index?PostCodeOne=123&PostCodeTwo=123 and both are failing, but I want to just pass the values to the CalculateMileage.
Thank you,
Don't redirect to the Index action, but return the Index view straight from the CalculateMileage action passing the model: return View("Index", model);
// controller
[HttpPost]
public IActionResult CalculateMileage(JourneyCalculator model)
{
model.DistanceValue = model.PostCodeOne + model.PostCodeTwo;
return View("Index", model);
}
I would also prefer to put the action/method on the form rather than the button:
<form asp-controller="Maps" asp-action="CalculateMileage" method="post">
<div class="form-row">
...
</form>
Alternatively, add a method to handle POST requests on the Index action:
[HttpPost]
public IActionResult Index(JourneyCalculator model)
{
model.DistanceValue = model.PostCodeOne + model.PostCodeTwo;
return View(model);
}

Why returning a new view doesn't reset form

I have an appointment class like this:
public class Appointment
{
public string ClientName { get; set; }
public DateTime Date { get; set; }
}
In the HomeController:
public IActionResult Index()
{
return View(new Appointment { Date = DateTime.Now }); //first time to get the form
}
and the default index.cshtml has a form inside and displays client name and date and when the submit button is pressed, it posts values to MakeBooking action method as:
public ViewResult MakeBooking(Appointment appt)
{
return View(); // second time to get the form
}
What I don't understand is, I ran the application and filled the form with some value, e,g client name is 'Michael', date is '20/08/2019' and pressed the submit button, and it is directed to MakeBooking action method, inside the action method, I returned a view with no data model. so on the second time, the form should have no value since I didn't pass the data model appt in the view, but why still I have 'Michael' and '20/08/2019' populated in the view?
This is the index.cshtml:
#model Appointment
#{ Layout = "_Layout"; }
<form class="m-1 p-1" asp-action="MakeBooking" method="post">
<div class="form-group">
<label asp-for="ClientName">Your name:</label>
<input asp-for="ClientName" class="form-control" />
</div>
<div class="form-group">
<label asp-for="Date">Appointment Date:</label>
<input asp-for="Date" type="text" asp-format="{0:d}" class="form-control" />
</div>
<button type="submit" class="btn btn-primary">Make Booking</button>
</form>
Html Helpers actually check in ModelState for the value to display in a field before they look in the Model.use this in the controller
ModelState.Clear();
return View();

Add parameters to the form in razor

I have this form in my view which leads me to some action in the controller. The thing is I don't know how to pass parameters to the action method.
<form class="full-search" asp-controller="Movies" asp-action="Search">
<input type="search" name="searchmovie" placeholder="Search..." class="search-form">
<select name="option" form="">
<option name="option" value="category1">category 1</option>
<option name="option" value="category2">category 2</option>
<option name="option" value="category3">category 3</option>
</select>
<i class="fas fa-caret-down"></i>
<input type="submit" name="" class="submit-full-search">
<div class="search-btn-submit">
<img src="~/img/search.svg">
</div>
</form>
And this is my controller:
[HttpGet("searchmoview/{option?}/{searchmovie}")]
public IActionResult Search(string option, string searchmovie)
{
//perform some search based on the filters
return View("Search", data);
}
But when I run my code, when I click on search the url looks like this:
https://localhost:33590/Information/Search
Instead of like this:
https://localhost:44320/seachmovie/category1/{searchString}
Any idea guys on how can I pass the parameters?
Long story short - there is no out of the box way of mapping form values to a route.
The best way to pass values back to your mvc controllers is to have a view model.
Create a search view model.
//View Model
public class SearchViewModel{
public string Query {get;set;}
public string Category {get;set;}
}
In your controller you'll want to pass this down to your view or partial view.
Use "ActionResult" in .NET and "IActionResult" in .Net Core
//Controller
public class SearchController{
[HttpGet]
public ActionResult SearchBox(){
return View();
}
[HttpPost]
public ActionResult SearchBox(SearchViewModel model){
//model is now populated with your values from your form.
//ex: model.Query
return View();
}
}
Create an enum for your categories, there is many other ways to do this but this one is the simplest.
//Enum
public enum Categories
{
Category1,
Category2
}
Now in your razor view bind your view model and you're good to go.
//Razor View
#model SearchViewModel
#using (Html.BeginForm("SearchBox", "Search", FormMethod.Post))
{
#Html.TextBoxFor(x => x.Query, new { #class = "search-form"})
#Html.DropDownListFor(x => x.Catergory,
new SelectList(Enum.GetValues(typeof(Categories))),
"Select Category", new { #class= "example-class"})
<i class="fas fa-caret-down"></i>
<input type="submit" name="" class="submit-full-search">
<div class="search-btn-submit">
<img src="~/img/search.svg">
</div>
}
If you want the form values to be posted to your url you can change it to FormMethod.Get
//Razor View
#model SearchViewModel
#using (Html.BeginForm("SearchBox", "Search", FormMethod.Get))
{
#Html.TextBoxFor(x => x.Query, new { #class = "search-form"})
#Html.DropDownListFor(x => x.Catergory,
new SelectList(Enum.GetValues(typeof(Categories))),
"Select Category", new { #class= "example-class"})
<i class="fas fa-caret-down"></i>
<input type="submit" name="" class="submit-full-search">
<div class="search-btn-submit">
<img src="~/img/search.svg">
</div>
}
If you change it to get, you'll also have to change your controller to expect these in your get method. You can also just bind the model and MVC will map the parameters back into the model for you automatically.
public class SearchController{
[HttpGet]
public ActionResult SearchBox(SearchViewModel model){
//if the model values are in the URL "model" will be populated, else it will be null.
return View();
}
}
You can't. That's not how things work. A form will by default send a POST request, in which case inputs in the form are sent as part of the request body (i.e. not the URL). You can set the method to GET, instead, but then the input values will be sent as part of the query string (i.e. ?foo=bar), not as part of your path, as you're looking for.
The only way to remotely achieve what you want is to use JavaScript to manipulate the form's action based on things like the category select box changing. You'd bind to the change event of that element, and then you'd alter the form element's action attribute in some way.

Controller action not being hit

I have the following form that is trying to let a User SignIn:
<form method="post" asp-controller="Auth" asp-action="SignIn">
<div class="form-group row">
<div class="col">
<input class="form-control" type="email" placeholder="Email" id="email-input" name="email">
</div>
</div>
<div class="form-group row">
<div class="col">
<input class="form-control" type="password" placeholder="Password" id="password-input" name="password">
</div>
</div>
<div class="form-group row">
<div class="col">
<button class="btn btn-success" type="submit" name="button" style="width: 100%;">Login</button>
</div>
</div>
</form>
As you can see the form is using a Controller named Auth and an action on the Controller named SignIn. Here is a sample of the Controller:
public class AuthController : Controller
{
public IActionResult Index()
{
return View(new SignInViewModel());
}
[HttpPost]
public async Task<IActionResult> SignIn(SignInViewModel model)
{
if (ModelState.IsValid)
{
if (await _userService.ValidateCredentials(model.Email, model.Password, out var user))
{
await SignInUser(user.Email);
return RedirectToAction("Index", "Home");
}
}
return View(model);
}
}
I've set a breakpoint on the line if (ModelState.IsValid) and it never gets hit. When I click the login button the page simply refreshes. I really cannot see why this breakpoint is not being hit when the Controller and the Action seem OK to me.
EDIT: SignInViewModel:
public class SignInViewModel
{
[Required(ErrorMessage = "Please Enter an Email Address")]
public string Email { get; set; }
[Required(ErrorMessage = "Please Enter a Password")]
public string Password { get; set; }
}
Snippet from Chrome:
So I've made a new project in an attempt to replicate the issue and it works perfectly for me.
I'm assuming you're not using Areas also? If you are using areas, add this attribute to your controller class:
[Route("AreaNameHere")]
Although if you are able to get to the index page then this isn't the issue.
Are your breakpoints valid? Are symbols being loaded? It could be refreshing due to the ModelState being invalid or the details being invalid and so it's returning the same view. You could use fiddler to see if it's actually making a request at all.
EDIT:
Do you have this line "#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers" in your _ViewImports.cshtml file?

Implementing a Simple Search Function in ASP.NET MVC

I'm trying to implement a basic search page in the web app I'm developing. Right now the page looks like this
When a user enters a last name, the controller gets called to search the backend Microsoft SQL Server database for all accounts with that Last name
Right now the HTML form looks like this
#using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post))
{
<form>
<div>
Last Name:<br>
<input type="text" id="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
</form>
}
It's supposed to call this controller
[HttpPost]
public void SearchAct()
{
Console.WriteLine();
}
which will eventually execute the search and then put the results on the page. However, I can't get the controller to be called. I set a break point on the WriteLine so I know its never getting there and I don't know what I'm doing wrong
Add name attribute to your text box. Form collection build based on only name attributes.
Change button type to submit, then it will post your form to controller.
#using (Html.BeginForm("SearchAct", "Home", FormMethod.Post))
{
<div>
Last Name:<br>
<input type="text" id="nameToFind" name="nameToFind">
<input type="submit" id="submitId" value="submit" />
</div>
}
#{
if(ViewBag.SearchKey != null)
{
<span>
Search Key: #ViewBag.SearchKey
</span>
}
}
Instead on Console.WriteLine() use ViewBag to send your required data back to view
Refer below actions
//Get Action for rendering view
public ActionResult SearchAct()
{
return View();
}
[HttpPost]
public ActionResult SearchAct(string nameToFind)
{
ViewBag.SearchKey = nameToFind;
return View();
}
Action parameter name and text box name attribute value must be same otherwise it will null
If your form contains multiple text box then read all input values form FromCollection or Request
[HttpPost]
public ActionResult SearchAct(FormCollection form)
{
ViewBag.SearchKey = form["nameToFind"];
return View();
}
correct your form HomeController should be just Home
#using (Html.BeginForm("SearchAct", "Home", FormMethod.Post))
{
<form>
<div>
Last Name:<br>
<input type="text" id="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
</form>
}
controller should accept the input parameter for your filter
[HttpPost]
public void SearchAct(string nameToFind)
{
// filter snomething
Console.WriteLine(nameToFind);
}
First: To create a form via using razor syntax:
#using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post))
{
}
That would generate a form like this:
<form method="post" action="/HomeController/SearchAct">
</form>
So, no need to create a nested form inside that.
#using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post))
{
<form> // <- here
<div>
Last Name:<br>
<input type="text" id="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
</form> // <- here
}
Second: HomeController would match the controller what the full name is HomeControllerController.
So, if you want to hit Home controller, remove Controller from HomeController in #using (Html.BeginForm("SearchAct", "HomeController", FormMethod.Post)) {}
Thirth: if you want to catch nameToFind, you can try:
[HttpPost]
public void SearchAct(string nameToFind)
{
Console.WriteLine();
}
Hope this help!
first of all we dont need to two form tag and you dont need to add controller suffix. remve that:
#using (Html.BeginForm("SearchAct", "Home", FormMethod.Post))
{
<div>
Last Name:<br>
<input type="text" id="nameToFind" name="nameToFind">
<input type="button" id="submitId" value="submit" />
</div>
}
Secondly if you need to search you need to add paramater same as the name of input type text :
[HttpPost]
public void SearchAct(string nameToFind)
{
Console.WriteLine();
}

Categories

Resources