#Html.ValidationSummary works on wrong page - c#

Using asp.net core razor. My current if statement is wrong, but it is the only way to get the error messages to show up. The current if statement is if the ModelState is not valid return to view. On the new view it shows the error messages. However, what I want is if the ModleState is not valid redirect to the Index.cshtml page and show the errors their. When I flipped around my if condition the error messages do not show up in the Index.cshtml page.
Here is my Controller.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using quotingDojo.Models;
namespace quotingDojo.Controllers
{
public class HomeController : Controller
{
// GET: /Home/
[HttpGet]
[Route("")]
public IActionResult Index()
{
return View();
}
[HttpPost]
[Route("quotes")]
public IActionResult Quotes(Home model)
{
if(!ModelState.IsValid)
{
return View();
}
//continue your code to save
return RedirectToAction("Index");
}
}
}
Here is my Index.cshtml
#model quotingDojo.Models.Home
<h1>Welcome to the Quoting Dojo</h1>
#using(Html.BeginForm("Quotes","Home"))
{
<p>#Html.ValidationSummary()</p>
<p>
<label>Your Name</label>
#Html.TextBoxFor(s=>s.Name)
#Html.ValidationMessageFor(s => s.Name)
</p>
<p>
<label>Your Quote</label>
#Html.TextAreaFor(d=>d.Quote)
</p>
<input type="submit" name="submit" value="Add my quote!"/>
}
<form action="quotes" method="get">
<input type="submit" name="submit" value="Skip to quotes!"/>
</form>
Here is my Quotes.cshtml where the error messages currently show up.
<h1>Test</h1>
<p>#Html.ValidationSummary()</p>
Here is my models page
using System.ComponentModel.DataAnnotations;
namespace quotingDojo.Models
{
public class Home
{
[Required(ErrorMessage ="Please enter your name")]
[DataType(DataType.Text)]
[MinLength(3)]
public string Name {get; set;}
[Required]
[MinLength(5)]
public string Quote{get; set;}
}
}

Your issue is here:
return View();
That will return a view named "Quotes" since your action method is named Quotes (this is MVC convention). You also do not pass a model to it so even if the view existed, it will not know of the errors.
Two ways to fix your issue:
1.
You have to pass the model to your Index view so you need to explicitly return the Index view.
if(!ModelState.IsValid)
{
return View("Index", model);
}
2.
I personally prefer this method. Name your first action which serves the original form the same as the one your are posting to and then you can do this (note: you will also need to rename the view):
// GET: /Home/
[HttpGet]
[Route( "" )]
public IActionResult Quotes() {
return View();
}
[HttpPost]
[Route( "quotes" )]
public IActionResult Quotes(Home model) {
if( !ModelState.IsValid ) {
return View(model);
}
//continue your code to save
return RedirectToAction( "Index" );
}
This way both of them return the view named Quotes so you do not have to explicitly mention it.

The standard practice is, If model state is not valid, you return the same view (which user submitted) where you will show the validation error messages. You just need to change your Quotes action method to Index action method.
[HttpPost]
[Route("quotes")]
public IActionResult Index(Home model)
{
if (!ModelState.IsValid)
return View();
// to do :continue saving
}
Make sure to update your form to post to this action.
Not sure why you want to redirect to another page where you want to show the errros.
If you absolutely want to show the error messages in another view which was loaded via RedirectToAction method call (hence a totally new GET call), You need to use TempData to transfer the errors.
public ActionResult Quotes(Home model)
{
if (!ModelState.IsValid)
{
List<string> errors = new List<string>();
foreach (var modelStateVal in ViewData.ModelState.Values)
{
foreach (var error in modelStateVal.Errors)
{
errors.Add(error.ErrorMessage);
}
}
TempData["errors"] = errors;
return RedirectToAction("Index");
}
//continue your code to save
}
And in your index view,
#if (TempData["errors"] != null)
{
var errors = (List<string>) TempData["errors"];
foreach (var error in errors)
{
<span class="alert warning">#error</span>
}
}

Related

ASP.NET MVC - T4 MVC - POST with Model

I have a controller that looks like this:
[RoutePrefix("items")]
public partial class ItemsController : Controller
{
public ActionResult Index()
{
var model = new MyApp.Models.Items();
return View(model);
}
// POST: /items/delete
[HttpPost]
[Route("delete")]
public ActionResult Delete(DeleteItemModel model)
{
// delete the item
model.Delete();
return RedirectToAction("Index");
}
}
In my Index.cshtml, I have the following:
#model MyApp.Models.Items
<h2>Hello</h2>
<form action="#Url.Action(MVC.Items.Delete())" method="post">
<button type="submit">delete</button>
</form>
The above is clearly a subset of my view. My main concern is, how to pass a DeleteItemModel from my form back to the Delete action. What am I missing? The MVC.Items.Delete path was generated from T4MVC. Yet, I can't figure out how to pass a model into it. Is there a fix here, or do I have to use some other approach entirely?

Search results getting lost on post back

I am new to this MVC Architecture so please execuse for basic or amature questions.
I need to implement a search functionality. I created an action in Home Controller SearchFNW(searchstring). In this method I got a list of ViewModels with searched data.
I am passing this list to Index.cshtml and trying to display results in a table.
I am able to get the View Model's List data to View.
Index.cshtml
#model List<First_Niagara_Works.ViewModels.AccountViewModel>
#{
#using (Html.BeginForm("Index", "Home"))
{
if (Model != null)
{
// code to populate table using model
}
}
}
Homecontroller.cs
public ActionResult SearchFNW(string FNWtodo, string SearchInput)
{
// all code to load viewmodels to list
return View(#"~/Views/Home/Index.cshtml", lstAccountVM);
}
public ActionResult Index()
{
return View();
}
Problem: After execution of this code it redirects to Layout.cshtml and from there it calls Index action again .. and started all over again with #Html.beginform(). so not able to see table data with searched results..
try:
[HttpPost]
public ActionResult Index(string FNWtodo, string SearchInput)
{
// all code to load viewmodels to list
return View(lstAccountVM);
}
public ActionResult Index()
{
return View();
}
Your form #using (Html.BeginForm("Index", "Home")) submits a POST back to the Index Action in the controller. You just need to use the Index action that captures the [HttpPost] and it will return the Index view but with your populated model.

Alternative to Response.Redirect with Request.Querystring

I've made a mvc4 project in Visual Studio Express 2012 for web. And there I've made a search function. And a view to show the result.
So normally I would have added this to the _Layout.cshtml.
if (Request["btn"] == "Search")
{
searchValue = Request["searchField"];
if (searchValue.Length > 0)
{
Response.Redirect("~/Views/Search/Result.cshtml?searchCriteria=" + searchValue);
}
}
And that doesn't work. What whould be the alternative to Response.Redirect in mvc4, which still allows me to keep the searchCriteria to be read with Request.Querystring at the Result.cshtml page.
You should be definetly doing this in your controller, making it return an ActionResult and returning a RedirectResult, i.e.:
public ActionResult Search(string searchCriteria) {
return Redirect("~/Views/Search/Result.cshtml?searchCriteria="+searchCriteria);
}
Btw, I'd also say don't use neither of the Request stuff (or even Redirects), but actions with parameters that MVC will bind automagically from POST or GET parameters. E.g, "www.something.com/search?searchCriteria=hello" will automatically bind the searchCriteria parameter to the Action handling /search. Or, "www.something.com/search/hello" will bind to the parameter defined into your Routing config.
A simple example would be something like this:
Index.cshtml:
#using (Html.BeginForm("Results", "Search", FormMethod.Get))
{
#Html.TextBox("searchCriteria")
<input type="submit" value='Search' />
}
Then the controller:
public class SearchController : Controller
{
public ActionResult Index()
{
return View();
}
public ActionResult Results(string searchCriteria)
{
var model = // ... filter using searchCriteria
return View(model);
}
}
model could be of type ResultsViewModel, which would encase everything you need to display the results. This way, your search is setup in a RESTful way - meaning it behaves consistently each time.

Passing a model object to a RedirectToAction without polluting the URL?

Here's what I'm trying to do:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(ContactModel model)
{
if (ModelState.IsValid)
{
// Send email using Model information.
return RedirectToAction("Gracias", model);
}
return View(model);
}
public ActionResult Gracias(ContactModel model)
{
return View(model);
}
All three action methods are in the same controller. Basically, a user type up some data in the contact form and I want to redirect them to a thank you page using their name in the Model object.
As the code is, it works, but the URL passed along with GET variables. Not ideal.
http://localhost:7807/Contacto/Gracias?Nombre=Sergio&Apellidos=Tapia&Correo=opiasdf&Telefono=oinqwef&Direccion=oinqef&Pais=oinqwef&Mensaje=oinqwef
Any suggestions?
Sounds like a solution for TempData!
[HttpPost]
public ActionResult Index(ContactModel model)
{
if (ModelState.IsValid)
{
// Send email using Model information.
TempData["model"] = model;
return RedirectToAction("Gracias");
}
return View(model);
}
public ActionResult Gracias()
{
ContactModel model = (ContactModel)TempData["model"];
return View(model);
}
Instead of doing
return RedirectToAction("Gracias", model);
You could do
[HttpPost]
public ActionResult Index(ContactModel model)
{
if (ModelState.IsValid)
{
// Send email using Model information.
return View("Gracias", model);
}
return View(model);
}
and remove your Gracias controller action. Using above the "Gracias" view will be displayed with your ContactModel model.
I don't see the need to have a separate controller action if it uses the same model and is a lock step part of the workflow ex. "a successful POST to Index will always result in the Gracias View being displayed"
You could also store the model in TempData (which is like a 1 request session state) but I don't see any point in doing that in your situation as it just complicates things
Thoughts?
The quick answer is don't pass the entire model but some identifier that you can use to retrieve the model from the repository:
[HttpPost]
public ActionResult Index(ContactModel model)
{
if (ModelState.IsValid)
{
// Send email using Model information.
return RedirectToAction("Gracias", model.ID);
}
return View(model);
}
public ActionResult Gracias(int contactID)
{
ContactModel model = new ContractRepository().GetContact(contactID);
return View(model);
}

url is incorrect when return View

Inside my controller i'm returning a View which works fine except that the Url is not what I expected because it's replacing with the name of the action method it's in.
http://hostname/Controller/SubmitTicket
instead of
http://hostname/Controller/Detail
And I can't do a redirect to action in this case.
public ActionResult Detail()
{
return View();
}
public ActionResult SubmitTicket()
{
return View("Detail");
}
<h2>Detail</h2>
<% using (Html.BeginForm("SubmitTicket", "Home"))
{ %>
<input id="Submit1" type="submit" value="submit" />
<% } %>
In MVC the URL used will always be based on the Action.
I think what is happening is you are doing a POST to http://hostname/Controller/SubmitTicket, and then returning the Detail view. In this case the URL will be the URL you submitted to.
Hence if you want a different URL, one option is you need to change the name of your SubmitTicket action or define a different route for it. But I don't think that will solve your problem.
If you are doing a post, and want to show the user a detail page after the post, use the Post/Redirect/Get pattern.
public ActionResult Detail()
{
return View();
}
public ActionResult SubmitTicket()
{
return RedirectToAction("Detail")
}

Categories

Resources