AddModelError Not Being Passed Back to Controller - c#

I am using the asp.net Razor with C#. I am trying to verify that a value entered is a currency but I can't seem to do it correctly.
This is in my PaymentModel:
[Required]
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
[Display(Name = "Payment Amount:")]
public decimal Amount { get; set; }
This is my Prepayment view:
#model SuburbanCustPortal.Models.PaymentModel.PrePayment
#{
ViewBag.Title = "Make a payment!";
}
<script>
$(function(){
$("#AccountId").change(function(){
var val=$(this).val();
$("#currentBalance").load("#Url.Action("GetMyValue","Payment")", { custid : val });
document.forms[0].Amount.focus();
});
});
</script>
<h2>Make a Payment</h2>
#using (Html.BeginForm("SendPayment", "Payment", FormMethod.Post))
{
#Html.ValidationSummary(true, "Please correct the errors and try again.")
<div>
<fieldset>
<legend>Please enter the amount of the payment below:</legend>
<div class="editor-label">
Please select an account.
</div>
#Html.DropDownListFor(x => x.AccountId, (IEnumerable<SelectListItem>)ViewBag.Accounts)
<div class="editor-label">
#Html.LabelFor(m => m.AccountBalance)
</div>
<div class="editor-field">
<label class="sizedCustomerDataLeftLabel" id="currentBalance">#Html.DisplayFor(model => model.AccountBalance) </label>
</div>
<div class="editor-label">
#Html.LabelFor(m => m.Amount)
</div>
<div class="editor-field focus">
#Html.TextBoxFor(m => m.Amount, new { #class = "makePaymentText" })
#Html.ValidationMessageFor(m => m.Amount)
</div>
<p>
<input id="btn" class="makePaymentInput" type="submit" value="Pay Now" onclick="DisableSubmitButton()"/>
</p>
</fieldset>
</div>
}
This is my Prepayment ActionResult:
[Authorize]
public ActionResult PrePayment(PaymentModel.PrePayment model)
{
var list = new List<SelectListItem>();
var custs = _client.RequestCustomersForAccount(User.Identity.Name);
foreach (var customerData in custs)
{
var acctno = customerData.Branch + customerData.AccountNumber;
var acctnoname = string.Format(" {0} - {1} ", acctno, customerData.Name);
// msg += string.Format("*** {0} - {1} ***{2}", customerData.AccountId, acctnoname, Environment.NewLine);
list.Add(new SelectListItem() { Text = acctnoname, Value = customerData.AccountId });
}
if (custs.Length > 0)
{
model.AccountBalance = String.Format("{0:C}", Decimal.Parse(custs[0].TotalBalance));
}
ViewBag.Accounts = list;
return View(model);
}
The post of the view calls SendPayment and this was my check at the begining of the view:
if (model.Amount == 0)
{
ModelState.AddModelError("Amount", "Invalid amount.");
return RedirectToAction("PrePayment", model);
}
I can't seem to get the PrePayment to get my error back that I sent from AddModelError. I changed it to:
if (model.Amount == 0)
{
ModelState.AddModelError("Amount", "Invalid amount.");
return View("PrePayment", model);
}
But it never calls the controller and the screen errors out since it doesn't have the data it is expecting.
How do I redirect back to the calling view with the errors?
==== ADDITIONAL INFO ====
Here is my PrePayment View:
[Authorize]
public ActionResult PrePayment(PaymentModel.PrePayment model)
{
var list = new List<SelectListItem>();
var custs = _client.RequestCustomersForAccount(User.Identity.Name);
foreach (var customerData in custs)
{
var acctno = customerData.Branch + customerData.AccountNumber;
var acctnoname = string.Format(" {0} - {1} ", acctno, customerData.Name);
// msg += string.Format("*** {0} - {1} ***{2}", customerData.AccountId, acctnoname, Environment.NewLine);
list.Add(new SelectListItem() { Text = acctnoname, Value = customerData.AccountId });
}
if (custs.Length > 0)
{
var amt =String.Format("{0:C}", Decimal.Parse(custs[0].TotalBalance));
model.AccountBalance = amt;
decimal namt;
if (decimal.TryParse(amt.Replace(",",string.Empty).Replace("$", string.Empty), out namt))
{
model.Amount = namt;
}
}
ViewBag.Accounts = list;
return View(model);
}

There are several issues needs to be addressed.
1. return View("PrePayment", model);
This will not call the controller, as the function name suggests, it only passing your object to the specified "View"(.cshtml file)
2. return RedirectToAction("PrePayment", model);
You will not persist modelstate data, because you are doing a redirect.
Suggested workflow which will solve your issue. At least it solved mine.
1. Get the form to post to "PrePayment" instead of SendPayment and you will create a new method with the following signature and have all you validation logic in the method
[Authorize]
[HttpPost]
public ActionResult PrePayment(PaymentModel.PrePayment model)
2. If everything goes well then redirect to the success/send payment page depending on your requirement
3. If somehow you need to pass model object onto the next action. Use TempData like following. This will temporarily persist the data till the next action. Then it get disposed:
TempData["payment"]=model;

May be you should add a data annotation to your property and use ModelState.IsValid property to validate it
[Required]
[DataType(DataType.Currency)]
[DisplayFormat(DataFormatString = "{0:F2}", ApplyFormatInEditMode = true)]
[Display(Name = "Payment Amount:")]
[Range(0.01, Double.MaxValue)]
public decimal Amount { get; set; }
In your POST method, check whether the validation is passed, if not send the model back to the view.
[HttpPost]
public ActionResult SendPayment(PrePayment model)
{
if(ModelState.IsValid)
{
//do the fun stuff
}
return View(model);
}

Related

Using a form submit button to go to an external link with two inputs in ASP.NET MVC

I'm very new to MVC and I'm not sure quite how the forms work. Here's what i want to do:
So I've got two text boxes and an enter button in a form I want to put two values into the textboxes and use them in my external link. Here's the code I am trying to use, i know it's not correct at all but should show you what I'm trying to do:
View:
#model Test.Models.Home
#{
ViewBag.Title = "Main";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm("GetMessage()", "Home", FormMethod.Post))
{
#Html.Label("Location1: ")
#Html.TextBoxFor(model => model.Location1)
#Html.ValidationMessageFor(model => model.Location1)
#Html.Label("Location2: ")
#Html.TextBoxFor(model => model.Location2)
#Html.ValidationMessageFor(model => model.Location2)
<button type="submit">Enter</button>
}
Controller:
using System.Web.Mvc;
using BBWebApp.Models;
namespace Test.Controllers
{
public class HomeController : Controller
{
public ActionResult Main()
{
//var name = new Home() { Name = "Google maps API" };
return View();
}
[HttpPost]
public ActionResult Location(Home model)
{
if (ModelState.IsValid)
{
//TODO: SubscribeUser(model.Email);
}
return View("Main", model);
}
[HttpPost]
public ActionResult GetMessage()
{
return Redirect("https://example.com/" + Location1 + "/" + Location2);
}
}
}
Model:
public class Home
{
public string Location1 { get; set; }
public string Location2 { get; set; }
public string Name { get; set; }
}
As you can see I'm trying to get the form to trigger the redirect function under getMessage() but obviously this code is flawed as I don't fully understand how it works. As i said I'm very new to MVC so any help with this would be much appreciated.
This code is very good for starting. Only have some minor problems:
On your view's using block, remove paranthesis from action method
GetMessages
like that
#using (Html.BeginForm("GetMessage", "Home", FormMethod.Post))
{
#Html.Label("Location1: ")
#Html.TextBoxFor(model => model.Location1)
#Html.ValidationMessageFor(model => model.Location1)
#Html.Label("Location2: ")
#Html.TextBoxFor(model => model.Location2)
#Html.ValidationMessageFor(model => model.Location2)
<button type="submit">Enter</button>
{
Then you need to get your model and use variables on your controller's method
[HttpPost]
public ActionResult GetMessage(Home model)
{
return Redirect("https://example.com/" + model.Location1 + "/" + model.Location2);
}
and you are good to go.

How to correct the route that results from running a Controller Action?

Below is a very simple MVC triplet, implemented to try the things out.
It seems to work except the URL I get after changing a Status code to say 500 and hitting Submit button is Error/StatusCode, not Error/StatusCode/500. How can I change it?
I appreciate it is a simple question, but I could not find correct key words to google out the answer.
Model
public class ErrorModel
{
[DisplayName("Status Code")]
public string StatusCode { get; set; }
public ErrorModel(string statusCode)
{
HttpStatusCode code;
if (! Enum.TryParse(statusCode, out code))
{
code = HttpStatusCode.NotFound;
}
StatusCode = ((int)code).ToString();
}
public ErrorModel(HttpStatusCode statusCode = HttpStatusCode.OK)
{
StatusCode = ((int)statusCode).ToString();
}
}
View
#using WebApplication1.Models
#model WebApplication1.Models.ExcelModel
#using (Html.BeginForm("StatusCode", "Error", FormMethod.Post))
{
<p>
<div class="editor-label">
#Html.LabelFor(model => model.StatusCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.StatusCode, null, "id")
</div>
<input type="submit" value="Submit" />
</p>
}
Controller
public class ErrorController : Controller
{
static readonly List<HttpStatusCode> ErrorCodes = new List<HttpStatusCode>(){
HttpStatusCode.Unauthorized, HttpStatusCode.Forbidden, HttpStatusCode.NotFound, HttpStatusCode.InternalServerError};
public ActionResult StatusCode(string id)
{
ViewBag.Message = "";
if ((id == null) || (ErrorController.AreEqual(HttpStatusCode.OK, id)))
{
return View("StatusCode", new ErrorModel(HttpStatusCode.OK));
}
foreach (HttpStatusCode errorCode in ErrorCodes)
{
if (ErrorController.AreEqual(errorCode, id))
{
return View("HttpError", new ErrorModel(errorCode));
}
}
ViewBag.Message = "Exception " + id
+ #" is not supported, see https://msdn.microsoft.com/en-us/library/system.net.httpstatuscode(v=vs.110).aspx for further details";
return View("HttpError", new ErrorModel(HttpStatusCode.InternalServerError));
}
static private bool AreEqual(HttpStatusCode httpCode, string statusCode)
{
return (statusCode == ((int)httpCode).ToString());
}
}
You are using POST method to submit the form. That will not include the form element values in the url, but in the request body.
If you want your form field values to be part of the URL, Change the form method to GET. When using GET,if a form is posted, the data sent to the server is appended to the URL as query string values.
#using (Html.BeginForm("StatusCode", "Error", FormMethod.Get))
{
<div class="editor-label">
#Html.LabelFor(model => model.StatusCode)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.StatusCode, null, "id")
</div>
<input type="submit" value="Submit" />
}
Now when user submits the form, browser will issue a GET request to the Error/StatusCode url with the form data appended to the url.
/Home/StatusCode?id=500
Dynamically changing the action's url could be a solution and the code below shows a simplest way to do that.
<script>
$(document).ready(function() {
$('form').submit(function(){
this.action = this.action + '/' + $('#StatusCode').val();
})
});
</script>

Passing an Object to a controller using BeginForm()

I have a strongly typed view and I'm trying to pass the input from a textbox upon a button click to an action using BeginForm. My code keeps passing a null object to the action method in the controller. How do I pass the object to the controller via the form ?
#using (#Html.BeginForm("GetQueueInfoWorkorder","Home", FormMethod.Post, new { id = Model}))
{
#Html.TextBoxFor(x=> x.ID);
<input type="Submit" value ="Search" class="ui-button-icon-secondary"/>
}
Here is the actionmethod :
[HttpPost]
public ActionResult GetQueueInfoWorkorder(UserResponse id)
{
//Check queue complete
int woNumber = Convert.ToInt32(id);
tb_QueueCompleted completed = db.tb_QueueCompleted.SingleOrDefault(x => x.WorkOrderNumber == woNumber);
if (completed != null)
{
var u = new UserResponse { ID = completed.QueueId.ToString() };
GetLogInfoCompleted(u);
return View("GetLogInfo");
}
//check queue pending
return View();
}
I think you're fairly close, but make these changes & it should work as expected:
Model:
public class UserResponse
{
public int ID { get; set; }
}
View:
#model UserResponse
#using (Html.BeginForm("GetQueueInfoWorkorder", "Home"))
{
#Html.TextBoxFor(x => x.ID);
<input type="Submit" value ="Search" class="ui-button-icon-secondary"/>
}
Action method:
public ActionResult GetQueueInfoWorkorder(UserResponse model)
{
int woNumber = model.ID;
//...
}
if the #model of your view is UserResponse , then on submission of this page the model (UserResponse) will automatically get submitted to the controller. Where have you declared the #model for the view.

MVC View not displaying validation errors

i've got some validations in my mvc project. However, when i sumbit an empty form or form where some required fields have not been entered, it does not stay within the same form/view and show the errors. I cant see any modal errors (i.e amount is required field)
for example, here are some attributes in my modal
[Required]
[StringLength(1, MinimumLength = 1)]
public string Period { get; set; }
[Required]
[DataType(DataType.DateTime)]
public System.DateTime ModifyDate { get; set; }
Here is my controller
[HttpPost]
public ActionResult StopScheduled([Bind(Prefix = "item")] BillPayModel model)
{
//getUsers();
try
{
if (ModelState.IsValid)
{
//save stuff into db
db.SaveChanges();
}
else
{
ModelState.AddModelError("", "Could not Stop Scheduled Payment");
}
}
catch (FormatException)
{
ModelState.AddModelError("", "Could not Stop Scheduled Payment");
}
return RedirectToAction("StopScheduled");
}
}
here is my view
#if (Model !=null)
{
if (Model.IsSuccess == true)
{
<span><center><font color = "green">Successfully Completed Transaction! </font></center></span>
}
else
{
#Html.ValidationSummary(true, "ERROR! Please make sure you have entered correct details");
}
}
#if (Model ==null)
{
using (Html.BeginForm("BillPay", "BillPay", FormMethod.Post, new {}))
{
#Html.ValidationSummary(true);
<div>#Html.LabelFor(model => model.AccountNumber)</div>
#Html.DropDownList("Accounts", "-- Select User --")
<div>#Html.LabelFor(model => model.PayeeID)</div>
#Html.DropDownList("PayeeID", "-- Select User --")
<div>#Html.LabelFor(model => model.Amount)</div>
<div>#Html.TextBoxFor(model => model.Amount,new {style = "width:150px"})
#Html.ValidationMessageFor(model => model.Amount)
</div>
<div>#Html.LabelFor(model => model.ScheduleDate) (i.e 20/10/2013 10:00)</div>
<div>#Html.TextBoxFor(model => model.ScheduleDate,new {style = "width:250px"})
#Html.ValidationMessageFor(model => model.ScheduleDate)
</div>
<div>#Html.LabelFor(model => model.Period)</div>
<div>#Html.TextBoxFor(model => model.Period,new {style = "width:150px"})
#Html.ValidationMessageFor(model => model.Period)
</div>
<input type="submit" value ="Submit" style="width:8%;height:5%"/>
<input type="reset" value ="reset" style="width:8%;height:5%"/>
}
}
else
{
}
The problem is that you are making a new request with RedirectToAction so the validation of your model set to valid. To solve your problem you need to do like this:
[HttpPost]
public ActionResult StopScheduled([Bind(Prefix = "item")] BillPayModel model)
{
try
{
if (ModelState.IsValid)
{
//save stuff into db
db.SaveChanges();
}
else
{
ModelState.AddModelError("", "Could not Stop Scheduled Payment");
}
}
catch (FormatException)
{
ModelState.AddModelError("", "Could not Stop Scheduled Payment");
}
return View(model);
}
And in the view you should change the Html.ValidationSummary excludePropertyErrors to false, like this:
if (Model.IsSuccess == true)
{
<span><center><font color = "green">Successfully Completed Transaction! </font></center></span>
}
else
{
#Html.ValidationSummary(false, "ERROR! Please make sure you have entered correct details");
}
To show them oneach input you need yoo change your if statement (Model==null).

Keeping model after form submission

I have the following model used for a quiz, I am trying to submit a form and pass the existing model back to the Action as it has already been initialized in the Index action.
public class QuizModel
{
private List<string> _Responses;
public List<string> Responses
{
get
{
if (_Responses == null)
{
_Responses = new List<string>() { "Response A", "Response B", "Response C", "Response D" };
}
return _Responses;
}
}
public int? SelectedIndex { get; set; }
public string Question { get; set; }
}
With the following View:
<div class="title">Question</div>
<span id="question">#Model.Question</span>
#if (!Model.UserHasAnswered)
{
using (Html.BeginForm("Submit", "Quiz", FormMethod.Post))
{
for (int i = 0; i < Model.Responses.Count; i++)
{
<div class="reponse">#Html.RadioButtonFor(m => m.SelectedIndex, i)#Model.Responses[i]</div>
}
<input type="submit" value="This is the value" />
}
}
else
{
<div id="explanation">#Model.Explanation</div>
}
And Controller...
//
// GET: /Quiz/
public ActionResult Index()
{
QuizModel model = new QuizModel()
{
Question = "This is the question",
Explanation = "This is the explanation",
UserHasAnswered = false
};
return PartialView(model);
}
//
// POST: /Quiz/Submit
[HttpPost]
public ActionResult Submit(QuizModel model)
{
if (ModelState.IsValid)
{
int? selected = model.SelectedIndex;
model.UserHasAnswered = true;
}
return View("Index", model);
}
When the model comes to the Submit action it only contains the SelectedIndex and not Question or Explanation properties. How can I tell my view to pass the original model it received back to the Submit action?
When you first display the index, your Question and Explanation are displayed correctly.
Then you submit the form, and Question and Explanation doesn't make it to the Controller Action.
That is because your FORM doesn't have input fields containing the Question and Explanation.
Add this to your form:
#Html.HiddenFor(x => x.Question)
#Html.HiddenFor(x => x.Explanation)
If the Explanation is editable by the user, instead of adding a Hidden for it do this:
#Html.TextAreaFor(x => x.Explanation)
Remember: All the info you need to send to your controller need to be in INPUTS inside of your FORM.
This way, your view will became:
<div class="title">Question</div>
<span id="question">#Model.Question</span>
#if (!Model.UserHasAnswered)
{
using (Html.BeginForm("Submit", "Quiz", FormMethod.Post))
{
#Html.HiddenFor(x => x.Question)
#Html.HiddenFor(x => x.Explanation)
for (int i = 0; i < Model.Responses.Count; i++)
{
<div class="reponse">#Html.RadioButtonFor(m => m.SelectedIndex, i)#Model.Responses[i]</div>
}
<input type="submit" value="This is the value" />
}
}
else
{
<div id="explanation">#Model.Explanation</div>
}
I believe your index action should be like below in your scenario :
public ActionResult Index(QuizModel model)
{
if(model == null)
{
model = new QuizModel()
{
Question = "This is the question",
Explanation = "This is the explanation",
UserHasAnswered = false
};
}
return PartialView(model);
}
Hope this will help !!

Categories

Resources