I've been working around on form submission and I ran into something I can't figure out. I know this issue has been posted many times but I can't seem to work things out.
MODEL:
public class IndexModel
{
public string techNo { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
}
CONTROLLER:
public ActionResult Index(IndexModel _oTechModel)
{
//some codes on pulling data from the database
return View("Index", _oTechModel);
}
[HttpPost]
public ActionResult ProcessTechnician(FormCollection form, string SubmitButton)
{
IndexModel _oTechModel = new IndexModel();
switch (SubmitButton)
{
case "save": //add new technician
{
}
break;
case "update": //update technician details
{
try
{
if (isValid(form["techNo"].ToUpper(), "technician") == false) { /*do nothing*/}
else
{
_GTechnician.TECHNICIAN_NO = form["techNo"].ToUpper();
_GTechnician.FIRSTNAME = form["lastName"].ToUpper(); //form is empty here
_GTechnician.LASTNAME = form["firstName"].ToUpper(); //form is empty here
_GTechnicianFacade.updateTechnician(_GTechnician, _GAppSetting.ConnectionString);
}
}
catch (Exception ex) { throw ex; }
}
break;
case "search": //search technician
{
try
{
if (isValid(form["techNo"].ToUpper(), "technician") == false) { /*do nothing*/}
else
{
//some codes here
_oTechModel.techNo = form["techNo"].ToUpper();
_oTechModel.firstName = fNameStr;
_oTechModel.lastName = lNameStr;
_oTechModel.setEnable = true;
}
}
catch (Exception ex) { throw ex; }
}
break;
case "delete":
{
}
break;
}
return RedirectToAction("Index", _oTechModel);
}
VIEW:
#model Maintenance_.Models.IndexModel
#using (Html.BeginForm("ProcessTechnician", "Home", FormMethod.Post))
{
<td>Technician No :</td>
#Html.TextBoxFor(m => m.techNo)
<button type="submit" name="SubmitButton" value="search"></button>
<td>First Name :</td>
#Html.TextBoxFor(m => m.firstName, new { #class = "form-control", style = "width:380px;" })
<td>Last Name :</td>
#Html.TextBoxFor(m => m.lastName, new { #class = "form-control", style = "width:380px;" })
<button name="SubmitButton" value="delete" type="submit">Delete</button>
<button name="SubmitButton" value="update" type="submit">Update</button>
<button name="SubmitButton" value="save" type="submit">Save</button>
}
When I hit the search button it submits the form and displays the firstname and lastname of the technician on the textbox but when I change the values of the textboxes right after I hit the update button it clears the textbox and the data has lost. What am I doing wrong?
Your first line in the post method is
IndexModel _oTechModel = new IndexModel();
You then redirect to the index page with this 'empty' model. (in the case Update statement you are not assigning any values to _otechModel)
you need to change couple of things.
First change parameter
FormCollection form
to
IndexModel _oTechModel
as you have passed IndexModel from the form so it will be automatically binded to the parameter _oTechModel.
Next remove the line.
IndexModel _oTechModel = new IndexModel();
Then change
RedirectToAction("Index", _oTechModel);
it should be. Look at the Documentation
RedirectToAction("Index", new {_oTechModel= _oTechModel});
Related
I have an ASP.NET MVC project with entities based on EF6 (model first). So my entities are all auto-generated for me. I have an entity, Site and I just want the user to select a Site before proceeding. I have tried a couple of ways, all of them work, but they seem very messy and unnecessary.
I was curious about the cleanest way to create a DropdownList of Sites, then get the selected site when the form is submitted (by Id or whatever other mechanism is better).
Currently I have:
The index where the user is asked to select a site:
public ActionResult Index()
{
ViewBag.Sites = new SelectList(db.Sites.ToList(), "Id", "Name");
return View();
}
The view:
#using (Html.BeginForm("SetSite", "Home"))
{
#Html.Label("sites", "Site:");
#Html.DropDownList("Sites", null, new { #class = "selectpicker" });
<div style="width:100%;height:25px"></div>
<button class="btn btn-default" style="width:100%">Go</button>
}
And the SetSite action, where the form is submitted
[HttpPost]
public ActionResult SetSite()
{
if (Request.Form["Sites"] != null)
{
Session["Site"] = db.Sites.Find(Request.Form["Sites"]);
return RedirectToAction("Home");
}
else
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
A few problems arise from this method. First, I really wanted to take advantage of the #model functionality in razor and point it towards my Site class, but since it's auto-generated, I don't want to go poking around and adding a whole bunch of view properties. (beggars can't be choosers?)
Second, the Request.Form['Sites'] returns a string, and converting it to and int is ugly as hell.
As I mentioned, I'd like to use the #model functionality with Html.DropDownListFor. Is that possible when working with a list of Sites from the DB?
How can I get this done cleanly?
Solution 1:-
Controller:-
private List<Country> GetCountries()
{
var list = new Entity.Result<List<Entity.Country>>();
list = _context.Countries.Select(tc => new Entity.Country
{
Id = tc.Id,
Name = tc.Name,
IsdCode = tc.Code,
}).ToList();
return list.Data.Select(x => new Country
{
id = x.Id,
Name = x.Name,
}).ToList();
}
HttpGet Method:-
public ActionResult Add(int id)
{
try
{
}
catch (Exception ex)
{
}
finally
{
ViewBag.countryList = GetCountries();
}
return View()
}
View Method:-
#Html.DropDownListFor(x => x.countryId, new SelectList(ViewBag.countryList, "id", "Name"), KahandasDhanji.Common.Localization.Application.Resources.ddlCountry,
new { #id = "ddlCountry", #rows = 1 })
In Http Post Form Submitimg u handle that model value in HTTPPOST Method.
Solution 2:-
FormCollection class we can capture the form's values within the controller.
[HttpPost]
public ActionResult Add(FormCollection form)
{
string strDDLValue = form["Sites"].ToString();
return View(MV);
}
Hope Its Work !!!
You can use a ViewModel to avoid converting the string value from Request.Form. Below is how your ViewModel class should look like
public class MyViewModel
{
public MyViewModel()
{
this.DropdownItems = new List<SelectListItem>();
}
public int SelectedSiteId { get; set; }
public List<SelectListItem> DropdownItems { get; set; }
}
Change the get action method in your controller as below
public ActionResult Index()
{
List<Site> sites = db.Sites.ToList();
MyViewModel model = new MyViewModel();
foreach(var site in sites)
{
model.DropdownItems.Add(new SelectListItem() { Text = site.Name, Value = site.ID.ToString() });
}
return View(model);
}
Add #model MyViewModel at the first line in your view code and use Html.DropDownListFor method to generate the dropdownlist
#model MyViewModel
#using (Html.BeginForm("SetSite", "Home"))
{
#Html.Label("SelectedSiteId", "Site:");
#Html.DropDownListFor(m => m.SelectedSiteId, Model.DropdownItems, new { #class = "selectpicker" })
<div style="width:100%;height:25px"></div>
<button class="btn btn-default" style="width:100%">Go</button>
}
The post action method in your controller should look like below. model.SelectedSiteId will be the selected value of the dropdownlist and the type is int so you won't have to do any conversion such as Convert.ToInt32(Request.Form['Sites']).
[HttpPost]
public ActionResult SetSite(MyViewModel model)
{
Session["Site"] = model.SelectedSiteId;
return RedirectToAction("Home");
}
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>
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)
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).
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 !!