I am confused by strange model binder behavior:
- when I use RedirectToAction with parameters which includes fractional number (because in Russian, we use "," instead of ".", e.x. 1,5; 2,5) in View it Binds that with dots, "4,5 => 4.5", and after post form I have ModelState error and values equal to 0. When inputting integer numbers however, all works as expected.
How i can fix it?
Models:
public class TestA
{
public double FirstNumberA { get; set; }
public double SecondNumberA { get; set; }
}
public class TestB
{
public double FirstNumberB { get; set; }
public double SecondNumberB { get; set; }
}
controller:
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(TestA model)
{
return RedirectToAction("About", new { firstNumberB = model.FirstNumberA, secondNumberB = model.SecondNumberA });
}
public ActionResult About(double firstNumberB, double secondNumberB)
{
return View(new TestB() { FirstNumberB = firstNumberB, SecondNumberB = secondNumberB });
}
[HttpPost]
public ActionResult About(TestB model)
{
return View();
}
views:
/* index */
#model TestA
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.FirstNumberA)
#Html.TextBoxFor(x => x.SecondNumberA)
<input type="submit" name="submit" value="submit" />
}
/* about */
#model TestB
#using (Html.BeginForm())
{
#Html.TextBoxFor(x => x.FirstNumberB)
#Html.TextBoxFor(x => x.SecondNumberB)
<input type="submit" name="submit" value="submit" />
}
Upd
When remove TextBoxFor(x => x.SecondNumberB) in About.cshtml and post form - FirstNumberB will cause an ModelState error, when SecondNumberB binds as should.
Upd2
I think, that here is another problem, that describe Phil Haack in his post - asp.mvc first look for values at the query string and get it. And in my case in query string I have ?firstNumber=1.5 and it binds to TextBox "as-is", without culture rules. And after post we get FormatException error. How can I fix it - bind decimal values to textboxes with correct culture decimal-separators? I can't use any JavaScript. I think about workaround with TempData before ReturnToRedirect, but in this case user can't reload the page.
Could be a culture problem. Make sure you set the correct culture in your web.config <globalization> element.
Related
I have created a DropDownList, as described in my last question here
I have been trying to figure out how to get the selected value of the list. I used the answer that was provided but the only thing it returned was {System.Web.Mvc.SelectList}
I debugged it and sure enough the string that was in the "Value" column was {System.Web.Mvc.SelectList}
What am I doing wrong here? I have been miserably failing at MVC and am new at it.
Thank you for the help
Your Action in your controller should look like this:
[HttpPost]
public ActionResult Index(int DropOrgId)
{
System.Diagnostics.Debugger.Break();
return null;
}
The important thing to note is that "DropOrgId" is the same as the string name you passed into #Html.DropDownList("DropOrgID") in your view. This name will store the value of the input from the HTML input control, in this case the
The source will be:
<select id="DropOrgID" name="DropOrgID">...</select>
The id of the input control is how the MVC framework will match up the value of that control to the parameter of the action you are looking for.
Here is a sample app that shows it:
Class
public class Organization
{
public int OrganizationID { get; set; }
public string Name { get; set; }
}
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var orgs = new List<Organization>();
foreach (var count in Enumerable.Range(1, 10))
{
var newOrg = new Organization();
newOrg.OrganizationID = count;
newOrg.Name = "Organization " + count.ToString();
orgs.Add(newOrg);
}
ViewBag.DropOrgID = new SelectList(orgs, "OrganizationID", "Name", 3);
return View();
}
[HttpPost]
public ActionResult Index(int DropOrgID)
{
//You can check that this DropOrgID contains the newly selected value.
System.Diagnostics.Debugger.Break();
return null;
}
}
Index View
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.DropDownList("DropOrgID")
<br />
<br />
<input type="submit" value="Save" />
}
Using Fiddler I can see that the request is not even being made but I can't see why.
Here's the form:
#using (Html.BeginForm("Index", "FileSystemChannelIndex", FormMethod.Post, new {
channelId = #Model.ChannelId }))
{
#Html.HiddenFor(model => model.Id)
#Html.HiddenFor(model => model.ChannelId)
<div class="editor-label">
Select File Source
</div>
<div class="editor-field">
#Html.DropDownListFor(
model => model.SelectedFileSourceValue,
new SelectList(Model.AvailableFilesSources, "Id", "Name"),
new { id = "selectFileSource" })
</div>
<p>
<input class="t-button" type="submit" value="Save" />
</p>
}
The View originally came from:
public ViewResult Create(int channelId)
{
var channel = this.fullUOW.GetFileSystemChannelRepository().All.Where(c => c.Id == channelId);
var vm = new FileSystemChannelIndexViewModel(channelId, new FileSystemChannelIndex());
return View("Edit", vm);
}
I've tried adding the "name" attribute to the but that didn't make any difference.
Any ideas?
EDIT: More info for Jim et al...
Domain:
public class FileSystemChannel
{
public int Id {get; set; }
public ICollection<FileSystemChannelIndex> ChannelIndexes { get; set; }
}
public class FileSystemChannelIndex
{
public int Id { get; set; }
public FileSystemChannel ParentChannel { get; set; }
}
Due to a 0...* association, in the UI we have to create a FileSystemChannel first then add a FileSystemChannelIndex to it. So that's why I pass in the channelId to the FileSystemChannelIndex Create View. When submitting the new FileSystemChannelIndex the following action should be called:
[HttpPost]
public ActionResult Index(int channelId, FileSystemChannelIndexViewModel vm)
{
//TODO: get the Channel, add the Index, save to db
return View("Index");
}
So thanks to Mark's comment it's due to a Select failing client side validation. Using IE dev tools to inspect the element:
<select name="SelectedFileSourceValue" class="input-validation-error" id="selectFileSource" data-val-required="The SelectedFileSourceValue field is required." data-val-number="The field SelectedFileSourceValue must be a number." data-val="true">
empo,
further to my comment above:
empo - can you post both public ActionResult Create(////) methods (i.e. HttpPost and HttpGet) into the question as this could highlight if the issue is related to ambiguous method signatures, which i suspect could well be the case as you are posting back the same signature as the HttpGet actionresult
try adding the appropriate HttpPost actionresult along the lines of:
[HttpPost]
public ActionResult Create(FileSystemChannelIndex domainModel)
{
if (!ModelState.IsValid)
{
return View(PopulateEditViewModel(domainModel));
}
_serviceTasks.Insert(domainModel);
_serviceTasks.SaveChanges();
return this.RedirectToAction("Edit", new {id = domainModel.ChannelId});
}
your original HttpGet (which feels 'wierd' to me):
[HttpGet]
public ViewResult Create(int channelId) {
var channel = this.fullUOW.GetFileSystemChannelRepository().All
.Where(c => c.Id == channelId);
var vm = new FileSystemChannelIndexViewModel(channelId,
new FileSystemChannelIndex());
return View("Edit", vm);
}
and inside your Edit actionresult, you'd grab the entity based on the passed in id. might work, might not. not sure without a fuller picture of your domain and logic.
obviously, your own plumbing will vary, but this should give an idea of what should be expected.
How can you have Model.Id when you are creating something? Maybe Model.Id is null and because you cannot post
I'm new to MVC3, but so far I have managed to get along with my code just great.
Now, I would like to make a simple form, that allows the user to input a text string, representing the name of an employee. I would then like this form to be submitted and stored in my model, in a sort of list. The form should then re-display, with a for-each loop writing out my already added names. When I'm done and moving on, I need to store this information to my database.
What I can't figure out, is how to store this temporary information, until i push it to my database. Pushing everytime I submit I can do, but this has cause me alot of headaches.
Hope you guys see what I'm trying to do, and have an awesome solution for it. :)
This is a simplified version of what I've been trying to do:
Model
public class OrderModel
{
public virtual ICollection<Employees> EmployeesList { get; set; }
public virtual Employees Employees { get; set; }
}
public class Employees
{
[Key]
public int ID { get; set; }
public string Name { get; set; }
}
View
#model OrderModel
#{
if (Model.EmployeesList != null)
{
foreach (var c in Model.EmployeesList)
{
#c.Name<br />
}
}
}
#using(Html.BeginForm())
{
#Html.TextBoxFor(m => m.Employees.Name)
<input type="submit" value="Add"/>
}
Controller
[HttpPost]
public ActionResult Index(OrderModel model)
{
model.EmployeesList.Add(model.Employees);
// This line gives me the error: "System.NullReferenceException: Object reference not set to an instance of an object."
return View(model);
}
I think you should handle this by burning the employee list into the page. Right now, you're not giving your form any way of recognizing the list.
In an EditorTemplates file named Employees:
#model Employees
#Html.HiddenFor(m => m.ID)
#Html.HiddenFor(m => m.Name);
In your view:
#using(Html.BeginForm())
{
#Html.EditorFor(m => m.EmployeesList)
#Html.TextBoxFor(m => m.Employees.Name)
<input type="submit" value="Add"/>
}
[HttpPost]
public ActionResult Index(OrderModel model)
{
if (model.EmployeesList == null)
model.EmployeesList = new List<Employees>();
model.EmployeesList.Add(model.Employees);
return View(model);
}
As an added bonus to this method, it would be easy to add ajax so the user never has to leave the page when they add new employees (You might be able to just insert a new hidden value with javascript and avoid ajax. It would depend on if you do anything other than add to your list in your post).
I think this would be a good use for TempData. You can store anything in there, kind of like the cache, but unlike the cache it only lasts until the next request. To implement this, change the action method like this (example only):
[HttpPost]
public ActionResult Index(OrderModel model)
{
dynamic existingItems = TempData["existing"];
if (existingItems != null)
{
foreach (Employee empl in existingItems)
model.EmployeesList.Add(empl );
}
model.EmployeesList.Add(model.Employees);
TempData["existing"] = model.EmployeesList;
return View(model);
}
Migrating from textboxe to dropdownlist – Need to send value from a hard-coded dropdownlist to controller
The code below is used in the controller
var list = new SelectList(new[]
{
new{ID="1",Name="20012"},
new{ID="2",Name="20011"},
new{ID="3",Name="20010"},
new {ID="4",Name="2009"},
new{ID="5",Name="2008"},
new{ID="6",Name="2007"},
new{ID="7",Name="2006"},
new{ID="8",Name="2005"},
new{ID="9",Name="2004"},
new{ID="3",Name="2003"},
new{ID="3",Name="2002"},
new{ID="3",Name="2001"},
new{ID="3",Name="2000"},
},
"ID", "Name", 1);
ViewData["list"] = listYear;
The code below is used in the view
#using (Html.BeginForm()){
<p>
Title: #Html.TextBox("SearchString")
#Html.DropDownList("list",ViewData["list"] as SelectList)
Genre: #Html.DropDownList("Towns", "All")
<input type="submit" value="Filter" /></p>
}
Below is the code which was used for the textbox
if (!String.IsNullOrEmpty(year))
{
car = Cars.Where(s => s.Year.Contains(year));
}
Looks like you are trying to select a value from a selectlist and send that value to the controller. First off, I'd suggest you use a ViewModel instead of magic strings. You should modify your View to accept the new ViewModel and then post the model to your action. It's easy, cleaner and more maintainable.
Here is what your model would look like
public class VehicleYearsViewModel {
public SelectList VehicleYears { get; set; }
public int SelectedYear { get; set; }
public VehicleYearsViewModel() {
VehicleYears = new SelectList(new[]
{
new{ID="1",Name="2012"},
new{ID="2",Name="2011"},
new{ID="3",Name="2010"},
new{ID="4",Name="2009"},
new{ID="5",Name="2008"},
new{ID="6",Name="2007"},
new{ID="7",Name="2006"},
new{ID="8",Name="2005"},
new{ID="9",Name="2004"},
new{ID="3",Name="2003"},
new{ID="3",Name="2002"},
new{ID="3",Name="2001"},
new{ID="3",Name="2000"}
}
}
}
Your View then would look like so:
#YourAppName.Models.VehicleYearsViewModel
#using (Html.BeginForm()){
#Html.ValidationSummary(true)
#Html.DropDownListFor(model => model.SelectedYear, Model.VehicleYears, "ID", "Name", 1))
<input type="submit" value="OK" />
}
Your controller action would accept the model and can make use of the selected value as an int datatype.
I'm just guessing since your controller action isn't posted but this is pretty much what it would look like:
public class HomeController : Controller {
public ActionResult Index() {
var model = new VehicleYearsViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(VehicleYearsViewModel model) {
if(ModelState.IsValid) {
// you can get selected year like so
int selectedYear = model.SelectedYear;
// ... your code here to do whatever with selectedYear
}
return View(model);
}
}
Hope this helps
Model:
using System.ComponentModel.DataAnnotations;
using MySite.Validators;
namespace MySite.Models
{
public class AddItem
{
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
[TagValidation(ErrorMessage = "At least one tag is required")]
public virtual List<int> Tags { get; set; }
}
}
View:
#using (Html.BeginForm()) {
...
<div class="editor-label">
#Html.LabelFor(model => model.Tags, "Tags")
</div>
<div class="editor-field">
#Html.ListBox("Tags")
#Html.ValidationMessageFor(model => model.Tags)
</div>
...
}
Validator:
using System.ComponentModel.DataAnnotations;
namespace MySite.Validators
{
public class TagValidationAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return false;
}
}
}
I want my validator to return false to begin with, just to make sure it's working. However, if I don't select any tags from the list and submit the form, it tries to process it without any errors indicating that I need to select a tag first.
What am I doing wrong here?
I had commented out the if (ModelState.IsValid == false) check in my controller, so I wasn't getting any validation. The reason I did this, initially, was because I was getting an error when I tried to pass the model back to the view, because the ListBox field in the view expected a IEnumerable and not a List.
Here's how I fixed both problems (in the controller):
[HttpPost]
public ActionResult AddItem(AddItem AddItem)
{
if (ModelState.IsValid == false)
{
ModelState.AddModelError("", "Model not valid.");
List<Tag> Tags = Db.Tags.ToList();
ViewBag.Tags = new SelectList(Tags, "TagId", "Name");
return View(AddItem);
}
//...
}
To get client side custom validation you need to implement in JQuery and i am assuming you are using ASP.net MVC 3 unobstrusive validation.
http://thepursuitofalife.com/asp-net-mvc-3-unobtrusive-javascript-validation-with-custom-validators/