Form submitting DropDownListFor with Required field - asp.net-mvc

I have a dropdownlist from a table called StampsCSEMonths_test. Im trying to make the dropdown required before submitting the form.
The problem it always submits the form no matter if I select or not.
What could be the issue?
View:
#model WINHRAdmin.Models.CTS_Stamps
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.DropDownListFor(model => model.stampscsemonths_id, ViewBag.StampsCSEMonths_test as IEnumerable<SelectListItem>, "", new { #class = "form-control " })
#Html.ValidationMessageFor(model => model.stampscsemonths_id, "", new { #class = "text-danger" })
}
Model:
namespace WINHRAdmin.Models
{
public class CommitmentToServiceModel
{
}
public class CTS_Stamps
{
[Required]
[Range(1,Int32.MaxValue,ErrorMessage ="MonthYerar is required")]
public int? stampscsemonths_id { get; set; }
}
}
Controller:
public ActionResult CTS_Stamps()
{
ViewData["StampsCSEMonths_test"] =
new SelectList((from s in _IntranetEntities.StampsCSEMonths_test.OrderByDescending(x => x.stampscsemonths_id).ToList()
select new
{
stampscsemonths_id = s.stampscsemonths_id,
FullName = s.month + "/" + s.year
}),
"stampscsemonths_id",
"FullName",
null);
LoadEmployeer();
return View();
}
[HttpPost]
public ActionResult CTS_Stamps(FormCollection collection)
{
var testvar = collection["stampscsemonths_id"];//after submission it does get the value of ID
return View();
}

Typically you would want to use a client-side library like jquery.validate to prevent form submission if it's invalid. Here is a good article on the subject: https://www.codeproject.com/Articles/718004/ASP-NET-MVC-Client-Side-Validation.
The usual pattern for server-side MVC validation is almost what you're doing. You put the [Required] attribute above your data model attribute. That's good. You have a #ValidationMessageFor element in your view. Also good.
You forgot, however, to check if the model is valid in your controller. Add a check for ModelState.IsValid before you process the results. Something like this:
if (!ModelState.IsValid)
{
return View(collection); // assumes collection is the model
}
If the user submits an invalid form, after the round-trip they will see a validation error message next to the missing field.
Hope that helps.

Related

Dropdownlist value is null after posting to controller in ASP.NET MVC

I can get all Roles plus actually Role for chosed user, but then When I posting to EditUser action, then Dropdownlist sends null.
I mean When the form posts to my controller, I get null from DropDownList.
Here is my Model
public class EditUserViewModel
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public List<SelectListItem> ApplicationRoles { get; set; }
public string ApplicationRoleId { get; set; }
}
Here is Action
[HttpGet]
public async Task<ActionResult> EditUser(string id)
{
EditUserViewModel model = new EditUserViewModel();
model.ApplicationRoles = RoleManager.Roles.Select(r => new SelectListItem
{
Text = r.Name,
Value = r.Id
}).ToList();
if (!String.IsNullOrEmpty(id))
{
ApplicationUser user = await UserManager.FindByIdAsync(id);
if (user != null)
{
var role = await UserManager.GetRolesAsync(user.Id);
var existingRole = role.First();
string existingRoleId = RoleManager.Roles.Single(r => r.Name == existingRole).Id;
model.Id = user.Id;
model.FirstName = user.FirstName;
model.ApplicationRoleId = existingRoleId;
ViewBag.RoleId = new SelectList(RoleManager.Roles, "Id", "Name", model.ApplicationRoleId);
}
}
return PartialView("_EditUser", model);
}
And here is DropDownlist from _EditUser.cshtml
<div class="form-group">
#Html.Label("Role typ", htmlAttributes: new { #class = "control-label col-md-6" })
<div class="col-md-12" title="Ange antal datorer som finns i lager">
#Html.DropDownList("RoleId", null, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.ApplicationRoles, "", new { #class = "text-danger" })
</div>
</div>
Getting null Only from DropDownList, not from #Html.EditorFor
/Thanks in advance!
Forms post back the name/value pairs of their successful form controls. Your generating a <select> element with name="RoleId" but you model does not contain a property named RoleId. Since you want to bind the selected option to the ApplicationRoleId role property, then you view needs to be
#Html.LabelFor(m => m.ApplicationRoleId)
#Html.DropDownListFor(m => m.ApplicationRoleId, Model.ApplicationRoles)
#Html.ValidationMessageFor(m => m.ApplicationRoleId)
Notes:
Your current #Html.Label(..) code does not create a label
associated with your dropdownlist (clicking on it will not set
focus)
The ValidationMessageFor() need to be applied to the property your
binding to, not the SelectList
Delete you ViewBag.RoleId = new SelectList(..) code. Your have
already assigned the selectlist to the ApplicationRoles property
(and you should never need ViewBag if have a view model anyway)
Because you are declare that only HttpGet methods are allow in that method of the controller. Thats why

Asp.net MVC ModelState.Isvalid returning false for Id

I'm watching this ASP.NET MVC course. I have a customer model with these following attribute.
public class Customer {
public int Id { get; set; }
[Required]
[StringLength(255)]
public string Name { get; set; }
[Display(Name = "Date of Birth")]
public DateTime? DateOfBirth { get; set; }
public bool IsSubscribedToNewsLetter { get; set; }
public MembershipType MembershipType { get; set; }
[Display(Name="Membership Type")]
public byte? MembershipTypeId { get; set; }
}
Note thate the Id has no Required data annotation. But in my database, the Id is primary key and Identity is true for the column.
There is a ViewModel consisting Customer and MembershipType models.
public class CustomerFormViewModel {
public IEnumerable<MembershipType> MembershipTypes { get; set; }
public Customer Customer { get; set; }
}
I have a View that creates new Customer with Name, DateOfBirth, MembershipType and IsSubscribedToNewsLetter fields. It takes the CustomerFormViewModel.
#using Vidly.Models
#model Vidly.ViewModel.CustomerFormViewModel
#{
ViewBag.Title = "New";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>#ViewBag.Message</h2>
#using (Html.BeginForm("Save", "Customer")) {
<div class="form-group">
#Html.LabelFor(m => m.Customer.Name,new{#class="control-label"})
#Html.TextBoxFor(m => m.Customer.Name, new { #class = "form-control" })
#Html.ValidationMessageFor(m=>m.Customer.Name)
</div>
<div class="form-group">
#Html.LabelFor(m => m.Customer.DateOfBirth, new { #class = "control-label"})
#Html.TextBoxFor(m => m.Customer.DateOfBirth,"{0:d MMM yyyy}", new { #class = "form-control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Customer.MembershipTypeId, new { #class = "control-label"})
#Html.DropDownListFor(m => m.Customer.MembershipTypeId,new SelectList(Model.MembershipTypes,"Id","Name"), "Select Membership Type", new { #class = "form-control" })
</div>
<div class="checkbox">
<label>
#Html.CheckBoxFor(m => m.Customer.IsSubscribedToNewsLetter) Subscribed To Newsletter?
</label>
</div>
#Html.HiddenFor(m=>m.Customer.Id)
<button type="submit" class="btn btn-primary">Save</button>
}
Here is my Save controller:
public ActionResult Save(Customer customer) {
if (!ModelState.IsValid) {
var viewModel = new CustomerFormViewModel {
Customer = customer,
MembershipTypes = _context.MembershipTypes.ToList()
};
return View("CustomerForm", viewModel);
}
if (customer.Id == 0 || customer.Id==null) {
_context.Customers.Add(customer);
}
else {
var CustomerInDb = _context.Customers.Single(c => c.Id == customer.Id);
CustomerInDb.Name = customer.Name;
CustomerInDb.DateOfBirth = customer.DateOfBirth;
CustomerInDb.IsSubscribedToNewsLetter = customer.IsSubscribedToNewsLetter;
CustomerInDb.MembershipTypeId = customer.MembershipTypeId;
}
_context.SaveChanges();
return RedirectToAction("Index", "Customer");
}
When I fill the CustomerForm view and click the submit button, the ModelState.Isvalid() method always comes false; resulting the first if statement of the Save method true. So I can't store any new customer.
I tried to debug the application by putting breakpoint on if (!ModelState.IsValid) and saw that the Id field is creating a error(saying "The Id field is required"). Why is it saying that Id is required when it isn't? Does the ModelState.IsValid method check the model at database level? I don't think so.
If I change the Customer model's Id property like this:public int? Id { get; set; } and change the if statement by this,if ((!ModelState.IsValid) && (customer.Id==null) ) the application works fine.
Is there any other solution of this Id problem?
I watched the same course and I am guessing the author updated since you watched it, as he demonstrated this exact type of issue. The issue is that when returning the View Model to the View on the New Action, the Customer property is not initialized so the Id is null hence the ModelState failure when trying to Save
Just change as below, so that when setting the viewModel, you initialize the Customer and the Id is then 0:
public ActionResult New()
{
var memberShipTypes = _context.MembershipTypes.ToList();
var viewModel = new CustomerViewModel
{
Customer = new Customer(),
MembershipTypes = memberShipTypes
};
return View("CustomerForm", viewModel);
}
Had a heck of a time with this, and created a workaround, but it seems that Mosh addresses this in a later section where he sets up the Movie Form.
https://codewithmosh.com/courses/222293/lectures/3684111
The short answer is to add #Html.Hidden("Movie.Id", (Model.Movie != null) ? Model.Movie.Id : 0) to the MovieForm.cshtml.
He then describes a way to avoid hard-coding "Movie.Id" into the view (see https://github.com/mosh-hamedani/vidly-mvc-5/commit/e5b994581931a079ad87418ddcf9338e808bd821#diff-e94a8dc96403203b00e58238bb80101c )
This is just a rough draft as I don't have access to VS right now. Anyway, modify your Save action like so:
public ActionResult Save(CustomerFormViewModel customerVM) {
if (!ModelState.IsValid) {
return View(customerVM);
}
if (customer.Id == 0) {
_context.Customers.Add(customerVM.Customer);
}
else {
var CustomerInDb = _context.Customers.Find(customerVM.Customer.Id);
CustomerInDb.Name = customer.Name;
CustomerInDb.DateOfBirth = customer.DateOfBirth;
CustomerInDb.IsSubscribedToNewsLetter = customer.IsSubscribedToNewsLetter;
CustomerInDb.MembershipTypeId = customer.MembershipTypeId;
}
_context.SaveChanges();
return RedirectToAction("Index", "Customer");
}
Oh and you can remove the following from the view since this is for create page:
#Html.HiddenFor(m=>m.Customer.Id)
hey change Id to CustomerId in model class.
i think just 'Id' may be treated as Primary Key of that model class.
we are doing the same course and i ran into the exact same problem lol.
i found a pretty nice workaround in my opinion.
just add these lines of code in the CustomerFormView.
instead of
#Html.HiddenFor(m=>m.Customer.Id)
Add:
{
if (Model.Customer == null)
{
<input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Customer_Id" name="Customer.Id" type="hidden" value="0" />
}
else
{
#Html.HiddenFor(m => m.Customer.Id)
}
}
for some reason i saw that when i try to add a new customer , the value of id is an empty string instead of zero.
therefore i changed it to zero manually in case the Customer object is null
(which will always be the case when adding a new customer.)
and it works fine for me.
let me know if you think this solution is problematic..
BTW Regarding your question : "the Id field is creating a error(saying "The Id field is required"). Why is it saying that Id is required when it isn't?"
Int data type is non nullable therefore it is implicitly required..same as the MembershipId (byte data type that doesnt have the [Required] annotation.)
I am also going through this course. i have got a same issue. Add this line in the customerviewform
if (Model.Customers !=null)
{
#Html.HiddenFor(m => m.Customers.Id)
}
Why i have add because while hiddenfor which it is used for editing purpose if you remove also it will be problem. So add this line it will we be work and in customer model add public int? Membershiptype. And another thing while adding a new customer if you got error at dropdownlist then add this line before validation area return
customer.MembershipTypes = _Context.MembershipTypeTableset.ToList(); add this line before View("CustomerForm", viewModel)
This example is taken from Mosh Hamedani's MVC 5 course. He explained the Customer Id issue in the chapter 55. This can be resolved by passing a new customer() object in New method while creating a CustomerFormViewModel.
Perhaps the problem is that the Id field is marked as an int and not int?. Putting a variable as int the Model automatically assumes there's going to be a value for this property since it's not marked nullable.
Try marking the Id Property is int? and see if the results are what you expect or not.
After seeing this question, I made a workaround of my problem. I just disabled my Id error in ModelState at the very beginning of Save action.
public ActionResult Save(Customer customer) {
ModelState["customer.Id"].Errors.Clear();
if ((!ModelState.IsValid) ) {
var viewModel = new CustomerFormViewModel {
Customer = customer,
MembershipTypes = _context.MembershipTypes.ToList()
};
return View("CustomerForm", viewModel);
}
if (customer.Id == 0) {
_context.Customers.Add(customer);
}
else {
var CustomerInDb = _context.Customers.Single(c => c.Id == customer.Id);
CustomerInDb.Name = customer.Name;
CustomerInDb.DateOfBirth = customer.DateOfBirth;
CustomerInDb.IsSubscribedToNewsLetter = customer.IsSubscribedToNewsLetter;
CustomerInDb.MembershipTypeId = customer.MembershipTypeId;
}
_context.SaveChanges();
return RedirectToAction("Index", "Customer");
}
Now my Application works fine.

submit button doesn't pass input from textbox

Basically i have an input box in which an user can type in his email, and a button that submits the email. I can press the button, and it redirects to my "details" page. However, the input from the texbox is not passed to my controller.
View:
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
<div class="form-group form-inline">
<label class="margin20">Sign up for newsletter</label>
#Html.TextBoxFor(Model => Model.Email, new { name= "mail", Class = "form-control", Style = "display:inline-block; max-width:200px", Placeholder="Example#Example.com" })
<input type="submit" class="btn btn-default" style="display:inline-block" id="emailSignup"/>
</div>
}
Controllers
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(string mail)
{
return RedirectToAction("details", new {address = mail });
}
public ActionResult details(string address)
{
EmailSignup person = new EmailSignup { Email = address};
return View(person);
}
}
i left the model out, because it basically is 1 property.
Your
#Html.TextBoxFor(Model => Model.Email, ...)
is generating an input with name="Email".
Note that new { name = "mail" } does absolutely nothing fortunately (look at the html your generating) because if it did, it would screw up the model binding process - the whole purpose of using the HtmlHelper methods is to bind to your model.
You could change the method to
[HttpPost]
public ActionResult Index(string email)
and the parameter will be correctly bound, however your method should be
[HttpPost]
public ActionResult Index(XXX model)
{
if (!ModelState.IsValid)
{
return View(model);
}
return RedirectToAction("details", new { address = model.Email });
}
where XXX is the model that you declared in the view (i.e. with #model XXX), so that you get correct model binding and can take into account validation.
Note also that you property should be
[Display(Name = "Sign up for newsletter")]
[Required("Please ...")] // assuming you want to ensure a value is submitted
[EmailAddress] // assuming you want a valid email
public string Email { get; set; }
and then the view will be
#Html.LabelFor(m => m.Email) / correctly generates a label associated with the input
#Html.TextBoxFor(m => m.Email, new { #class = "form-control", placeholder="Example#Example.com" })
#Html.ValidationMessageFOr(m => m.Email)
and I recommend adding another class name and using css rather than you inline style = ".." element

MVC 5 Multiselect List Values Not Binding to Model On Post

I have a view with several multiselect lists which are declared like this
<div class="col-md-6">
#Html.LabelFor(model => model.Counties, htmlAttributes: new { #class = "control-label" })
#Html.ListBoxFor(model => model.Counties, new MultiSelectList(ViewBag.CountyList, "Value", "Text"), htmlAttributes: new { #class = "form-control", size = 8, tabindex = 26 })
#Html.ValidationMessageFor(model => model.Counties, "", new { #class = "text-danger" })
<span class="small">Ctrl + click to select multiple items</span>
</div>
My view model contains a declaration like this:
public virtual List<long> Counties { get; protected set; }
My action looks like this:
[HttpPost]
public ActionResult Edit(TScholarshipView model, FormCollection form)
{
if (ModelState.IsValid)
{
TScholarship scholarship = Repo.GetScholarship(model.Id);
scholarship = Mapper.Map<TScholarshipView, TScholarship>(model, scholarship);
Repo.SaveOrUpdate(scholarship, HttpContext.User.Identity.Name);
return RedirectToAction("Edit", "AdminScholarship", new { id = model.Id });
}
return View("Scholarship", model);
}
On submit I can look at the post data sent by the browser and it is sending the appropriate data to the server
...&Counties=16&Counties=34&...
When the action begins to execute the value of Counties in the model is null. However I can look at the FormCollection values and the value of form["Counties"] is "16,34". Any ideas why the binding is not occurring?
I noticed this right after I posted the question. The problem was having the setter protected. This prevented the binder from setting the value of the list.
You need to reset the value of ViewBag.CountyList on post event as well.
Or have one property in the model and bind that property to your multi select list box.
Something like
Wrapper / Model
public class CustomerList
{
public List<Customer> Customers { get; set; }
public List<int> SelectedIDs { get; set; }
}
Controller
[HttpGet]
public ActionResult DisplayCustomer()
{
Customer oCustomer = new Customer();
List<Customer> CustomersList = new List<Customer>();
CustomersList.Add(new Customer() { ID = 1, Name = "TestCustomer1", Amt = 123 });
CustomersList.Add(new Customer() { ID = 2, Name = "TestCustomer2", Amt = 234 });
CustomersList.Add(new Customer() { ID = 3, Name = "TestCustomer3", Amt = 324 });
ViewBag.CustList = CustomersList;
return View(new CustomerList() { Customers = CustomersList });
}
[HttpPost]
public void DisplayCustomer(List<int> selectedIds)
{
// do something with the id list
}
View
#model MvcApplication2.Models.CustomerList
#using (Html.BeginForm(#Model.SelectedIDs))
{
#Html.ListBoxFor(m => m.SelectedIDs, new MultiSelectList(#Model.Customers, "ID", "Name", #Model.SelectedIDs))
<input type="submit" value="save" />
}
As mentioned here
Hope that works!!!

Pass Select into Controller via Response

Hy,
I'm new to ASP.NET MVC 5. I'm trying to get the value of an HTML select with no success.
My View (essential part):
<div class="form-group">
#Html.Label("Country", new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.DropDownList("Countries", (IEnumerable<SelectListItem>)ViewBag.Countries, new { #class = "form-control", id = "Country", name = "Country" })
</div>
</div>
My Controller (essential part):
public ActionResult Index()
{
string country = Request["Country"]; // here I always get null
}
I need a newbie like explanation why this is not working and how I get it to work, please :)
First, I agree with #Maess. Don't use ViewBag. It's horrible and someone at Microsoft should be slapped for ever adding it as an option in the first place.
That said, your error is pretty obvious here. You named your select "Countries" and you're trying to pull "Country" out of the request.
Since you're new, I'll be nice and lay out how to use a view model for this. First, create a model:
public class IndexViewModel
{
public int SelectedCountry { get; set; }
public IEnumerable<SelectListItem> CountryChoices { get; set; }
}
Then in your action:
// GET
public ActionResult Index()
{
var model = new IndexViewModel();
// get your country list somehow
// where `Id` and `Name` are properties on your country instance.
model.CountryChoices = countries.Select(m => new SelectListItem { Value = m.Id, Text = m.Name });
return View(model);
}
And in your view:
#model Namespace.IndexViewModel
...
#Html.DropDownListFor(m => m.SelectedCountry, Model.CountryChoices, new { #class = "form-control" })
And finally, in your POST action:
[HttpPost]
public ActionResult Index(IndexViewModel model)
{
// use model.SelectedCountry
}

Resources