Keeping model after form submission - c#

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 !!

Related

Passing data from HttpGet to View to HttpPost in MVC [duplicate]

This question already has answers here:
Post an HTML Table to ADO.NET DataTable
(2 answers)
Closed 4 years ago.
I ran out of options checking why my HttpPost parameter is always null.My Models, View and Controllers are below. In general, I have a model with all details of medication administered to patients (Model 1) below. Then, I created another model with one property which is a list of my first model (Model 2). Model 2 is being passed to the view as shown in the HttpGet action. My HttpPost action accepts a parameter which is a list of Model 1. This list is being passed in my view in the following line which is within the View:
using (Html.BeginForm("ClientChargeInput", "Home", new { #vm = Model.patients.ToList() }, FormMethod.Post, null))
Any feedback?
Model
public class ControlledSubstancesEntity
{
public string facility { get; set; }
public string program_x_rrg_value { get; set; }
public string PATID { get; set; }
//More properties
}
Model being passed to View
public class ControlledSubsViewModel
{
public List<ControlledSubstancesEntity> patients { get; set; }
}
HttpGet Controller Action
[HttpGet]
public ActionResult ClientChargeInput(DateTime adminDate, string Facility)
{
ControlledSubsViewModel vm = new ControlledSubsViewModel();
//Some logic that populates vm
return View(vm);
}
View
#model ControlledSubstancesChargeInput.Models.ControlledSubsViewModel
#{
ViewBag.Title = "ClientChargeInput";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#if (Model != null && Model.patients.Count() > 0)
{
using (Html.BeginForm("ClientChargeInput", "Home", new { #vm = Model.patients.ToList() }, FormMethod.Post, null))
{
<input type="submit" value="Charge Input" id="btnSubmit" class="btn btn-info btn-sm" onclick="PostCharge()" />
#*Fields are shown*#
#foreach (var p in Model.patients)
{
#Html.HiddenFor(model => p.facility)
#Html.HiddenFor(model => p.program_x_rrg_code)
#Html.HiddenFor(model => p.program_x_tx_setting_code)
}
}
}
else
{
#*#Html.Action("ShowReport");*#
}
HttpPost Controller Action
[HttpPost]
public ActionResult ClientChargeInput(List<ControlledSubstancesEntity> vm)
{
//On breakpoint vm is always null
}
You can try to use ControlledSubsViewModel instead of List<ControlledSubstancesEntity> class, then you can use vm.patients to do your logic.
because you use ControlledSubsViewModel in the razor view that pass the parameter format will like ControlledSubsViewModel.
[HttpPost]
public ActionResult ClientChargeInput(ControlledSubsViewModel vm)
{
//vm.patients use this property
}
Because Model.patients is a collection, you can use for loop set input tag (hidden or text) look like this.
#if (Model != null && Model.patients.Count() > 0)
{
using (Html.BeginForm("ClientChargeInput", "Home",FormMethod.Post))
{
<input type="submit" value="Charge Input" id="btnSubmit" class="btn btn-info btn-sm" onclick="PostCharge()" />
for (int i = 0; i < Model.patients.Count; i++)
{
#Html.TextBoxFor(m => Model.patients[i].PATID);
#Html.TextBoxFor(m => Model.patients[i].facility);
#Html.TextBoxFor(m => Model.patients[i].program_x_rrg_value);
}
}
}
else
{
#*#Html.Action("ShowReport");*#
}
You still need to create ControlledSubstancesEntity as hidden-fields even if you do not want to display them on the page.
#using (Html.BeginForm("Index", "Home", FormMethod.Post, null))
{
for (int i = 0; i < Model.patients.Count; i++)
{
#Html.HiddenFor(x => Model.patients[i].facility)
#Html.HiddenFor(x => Model.patients[i].program_x_rrg_value)
#Html.HiddenFor(x => Model.patients[i].PATID)
}
<input type="submit" value="Charge Input" id="btnSubmit"
class="btn btn-info btn-sm" onclick="PostCharge()" />
}
Then use ControlledSubsViewModel as a parameter in HttpPost action method.
(click on the image to view in full screen)

MVC 5 send radio button string value to post action method

I've read a couple of questions and answers on this matter but none of them gets me any closer to solving this issue. My problem is I can't seem to retain the RadioButtonFor checked value to the post action method. It's alot of unrelated stuff so i'll try to boil it down to the related issue.
So I've got a ViewModel like this:
public class QEventHandlerVM
{
//Removed unrelated
public QueueEventHandlerModel QEventHandler { get; set; }
public string SelectedOption { get; set; }
}
The Get Method for the edit view. I'm setting the SelectedOption here so I can work with it on the frontend. The task is to show and hide div depending on the SelectedOption value. This part is working as far as I know, the value gets set and is used in the view via jQuery.
[HttpGet]
public ActionResult Edit(int id)
{
try
{
//Removed unrelated...
QueueEventHandlerModel model;
QEventHandlerVM vm = new QEventHandlerVM();
ApiCommunicator.Get("QueueEventHandlers/" + id, out model);
vm.QEventHandler = model;
if (...)
vm.SelectedOption = "toQueue";
if (...)
vm.SelectedOption = "redirection";
if (...)
vm.SelectedOption = "hangup";
return View(vm);
}
catch (HttpResponseUnsuccessfulException e){...}
}
The Edit view simplified:
#model Foo.WebSites.Web.Views.QueueEventHandlers.QEventHandlerVM
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
//Unrelated stuff...
#Html.HiddenFor(s => s.SelectedOption)
#Html.RadioButtonFor(s => s.SelectedOption, "toQueue", new { id = "rdToQueue", #class = "q-event" })
<label for="rdToQueue">Transfer call to queue</label>
#Html.RadioButtonFor(s => s.SelectedOption, "redirect", new { id = "rdRedirect", #class = "q-event" })
<label for="rdRedirect">Transfer call to destination</label>
#Html.RadioButtonFor(s => s.SelectedOption, "hangup", new { id = "rdHangup", #class = "q-event" })
<label for="rdHangup">Hangup</label>
<button type="submit" class="btn btn-primary btn-sm">Save</button>
}
The Post method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(QEventHandlerVM model, int id)
{
if (!ModelState.IsValid) {... return View(model); }
if (model.SelectedOption == "hangup" || model.SelectedOption == "redirection")
{
model.QEventHandler.QueueId = null;
}
else if (model.SelectedOption == "toQueue")
{
model.QEventHandler.Phrase = string.Empty;
model.QEventHandler.Redirection = string.Empty;
}
else if (model.SelectedOption == "hangup")
{
model.QEventHandler.Redirection = string.Empty;
model.QEventHandler.QueueId = null;
}
try {// Update}
catch (HttpResponseUnsuccessfulException e){...}
}
Now, here the SelectedOption value is the same as it was from the Get method. There's probably something simple I've missed and there's most likely some better way to go about this.
Thanks in advance.
That's because you have a HiddenFor and associated edit fields for your SelectedOption property. Simply remove the HiddenFor and that should work:
#Html.HiddenFor(s => s.SelectedOption)

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.

In MVC, posting of form return empty model

I have the following viewModel
public class ExerciceViewModel
{
public string Code { get; set; }
public string Titre { get; set; }
public int QuestionCourante { get; set; }
}
the following view
#model MonEcoleVirtuelle.ViewModel.ExerciceViewModel
#{
ViewBag.Title = "Test";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Test</h2>
#using (Html.BeginForm("test", "exercice", FormMethod.Post))
{
#Model.Code <br />
#Model.Titre<br />
#Model.QuestionCourante<br />
<br />
<br />
Model.Code = "Code Modifie";
<input type="submit" value="Post Moi Ca!" name="fini" />
}
and the following controller methods
[HttpPost]
public ActionResult Test(ViewModel.ExerciceViewModel model)
{
if (ModelState.IsValid)
{
return Content(model.Code);
}
return View(model);
}
[HttpGet]
public ActionResult Test()
{
var vm = new ViewModel.ExerciceViewModel { Code = "code1", Titre = "Mon titre", QuestionCourante = 1 };
return View(vm);
}
When I submit the form, the model passed is empty, all properties are reset, not keeping the original values. What am I missing.
thanks
well, instead of #Model.Code which just display the values, you need some inputs.
So #Html.TextBoxFor(m => m.Code) for example
To manage a collection, you can do something like that :
#for (var i = 0; i < Model.Collection.Count; i++) {
Html.TextBoxFor(m => Model.Collection[i].Property)
}
You have not included any input fields in your view.
The #Model.Code etc only output the value of the field. To be able to post back elements they need to be form elements, like inputs. Use something like
#Html.TextBoxFor(p=>p.Code)
to create input fields that can then be posted back.
For a more complete guide see MSDN at http://msdn.microsoft.com/en-us/library/dd410596(v=vs.100).aspx

AddModelError Not Being Passed Back to Controller

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);
}

Categories

Resources