I am trying to clear the selected value from the dropdown but still the value is persisting. It is the same behavior using #Html.DropDownListFor
Controller
public class HomeController : Controller
{
[Route("/Home/Index")]
[Route("/Home/Index/{Category}")]
[Route("/Home/Index/{Category}/{Type}")]
public IActionResult Index(HomeModel model)
{
// Issue is here
// for url: home/index/accessories/test
// "Category" is cleared if it is not valid "type"
// but still "Accessories" remains selected in the drop down
if (model.Type != "Electronics" && model.Type != "Furniture")
{
model.Category = string.Empty;
}
return View(new HomeModel() { Category = model.Category, Type = model.Type });
}
View
#model WebApplication1.Controllers.HomeModel
<select asp-for="Category" asp-items="#Model.Categories"></select>
<select asp-for="Type" asp-items="#Model.Types"></select>
Model
public class HomeModel
{
public string Category { get; set; }
public string Type { get; set; }
public List<SelectListItem> Categories { get; set; } = new List<SelectListItem>
{
new SelectListItem() { Text="Computers & Laptops", Value="Computers-Laptops" },
new SelectListItem() { Text="Accessories", Value="Accessories" },
};
public List<SelectListItem> Types { get; set; } = new List<SelectListItem>
{
new SelectListItem() { Text="Electronics", Value="Electronics" },
new SelectListItem() { Text="Furniture", Value="Furniture" },
};
}
UPDATE
I tried to add a empty value to the Category dropdown and still no luck.
<select asp-for="Category" asp-items="#Model.Categories">
<option value="">Select Category</option>
</select>
The issue is within the ModelState. What happens is that when the URL parameters are bound to the view model, the values are added to the model state dictionary. When the view is rendered, not only your HomeModel is passed to the view, but underneath the scenes the ModelState is passed as well - and it has a higher priority than the view model. So while you reset the model.Category, the Category key in the ModelState still has the value accessories and that is why it is being selected in the dropdown.
The solution is simple, call .Clear() to wipe out the model state and make your view model win:
[Route("/Home/Index")]
[Route("/Home/Index/{Category}")]
[Route("/Home/Index/{Category}/{Type}")]
public IActionResult Index(HomeModel model) {
// Issue is here
// for url: home/index/accessories/test
// "Category" is cleared if it is not valid "type"
// but still "Accessories" remains selected in the drop down
if (model.Type != "Electronics" && model.Type != "Furniture") {
model.Category = string.Empty;
}
ModelState.Clear();
return View(new HomeModel() { Category = model.Category, Type = model.Type });
}
Related
This is done by adding a view from my controller and selecting my dto as template
My DTO
public class Company_DTO
{
public long ID_Company { get; set; }
public string ESTATE_Company { get; set; }
}
MyController
public ActionResult UpdateCompany()
{
ViewBag.ListOfCompanies = DependencyFactory.Resolve<ICompanyBusiness>().GetCompany(); // this return a List<int> and following what I read for viewbag this should be right.
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult UpdateCompany([Bind]Company_DTO company_DTO)
{
try
{
//code
}
catch
{
return View();
}
}
View
<div class="form-group">
#Html.DropDownListFor(model => model.ID_Company , ViewBag.ListOfCompanies) // Here I get an error on my #Html that my dto does nothave a list.
</div>
I want the selected item to be ID_Company, but here it seems to be trying to add the whole list when I just want the selected item, I cant find any documentation or question that can solve my issue.
I Cant EDIT the DTO.
Thanks for any help and hope I am being clear enough.
This should solve your problem:
View
<div class="form-group">
#Html.DropDownListFor(model => model.ID_Company, new SelectList(ViewBag.Accounts, "ID_Company", "ESTATE_Company"))
</div>
Supposing your view is strongly typed (#model Company_DTO).
Hope this helps
consider the following example:
public class HomeController : Controller
{
private List<SelectListItem> items = new List<SelectListItem>()
{
new SelectListItem() { Text = "Zero", Value = "0"},
new SelectListItem() { Text = "One", Value = "1"},
new SelectListItem() { Text = "Two", Value = "2"}
};
public ActionResult Index()
{
ViewBag.Items = items;
return View(new Boo() { Id = 1, Name = "Boo name"});
}
}
public class Boo
{
public int Id { get; set; }
public string Name { get; set; }
}
the view:
#model WebApi.Controllers.Boo
#Html.DropDownListFor(x=>x.Id, (IEnumerable<SelectListItem>) ViewBag.Items)
so, ViewBag.ListOfCompanies should contain IEnumerable. Each SelectListItem has Text and Value property , you need to assign ESTATE_Company and ID_Company respectively. something like this:
var companiesList = //get companies list
ViewBag.ListOfCompanies = companiesList.Select(x => new SelectListItem() {Text = x.ESTATE_Company, Value = x.ID_Company.ToString()});
....
#Html.DropDownListFor(x=>x.ID_Company, ViewBag.Items as IEnumerable<SelectListItem>)
I wish to utilize the ASP.NET MVC convention to parse POSTed form submission to a model by ActionResult MyAction(MyModel submitted). MyModel includes a property of the type that I defined -UsState-.
submitted.UsState as the result of the action returns null.
Is there any way to get the submitted to be set to proper value?
I have the following view with a form in a MyForm.cshtml
...
#using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, ...))
{
#Html.DropDownList("States",
null,
new { #id = "state", #class = "form-control", #placeholder = "State"})
}
...
with the controller
public class MyController : Controller
{
public ActionResult MyForm()
{
ViewBag.States = GetStatesList();
}
public ActionResult MyAction(MyModel info) //info.State is set to null on POST
{
return View();
}
private static List<SelectListItem> GetStatesList()
{
var states = new List<SelectListItem>();
states.Add(new SelectListItem { Value = "", Selected = true, Text = "State" });
foreach (var s in UsStates.ToList())
{
var state = new SelectListItem { Value = s.Abbreviation, Text = s.Name, Disabled = !s.Available };
states.Add(state);
}
return states;
}
}
with the model
public class MyModel
{
public UsState States { get; set; } //Do I need proper setter for it to work?
}
public static class UsStates //Should I get the UsStates in a different way?
{
private static List<UsState> states = new List<UsState> {
new UsState("AL", "Alabama"),
//...
}
public static List<UsState> ToList()
{ return states; }
}
public class UsState
{
public UsState(string ab, string name, bool available = true)
{
Name = name;
Abbreviation = ab;
Available = available;
}
}
Change you MyModel to
public class MyModel
{
public string SelectedState {get;set; }
public IEnumerable<SelectListItem> States { get; set; }
}
modify your view to
#using (Html.BeginForm("MyAction", "MyController", FormMethod.Post, ...))
{
#Html.DropDownListFor(m => m.SelectedState, Model.States, "State",
new { #class = "form-control" })
}
I changed this to a DropDownListFor, because it will automatically generate the id and name attributes for you. It will also create an element at the top "State" so that you don't have to pre-pend it to your list.
I removed your placeholder because placeholders are not supported with select elements. As well as not to be used as replacements for labels.
Per W3C
The placeholder attribute specifies a short hint that describes the
expected value of an input field (e.g. a sample value or a short
description of the expected format).
Split your controller actions to a GET/POST pair
public class MyController : Controller
{
//Get
public ActionResult MyAction()
{
return View(new MyModel { States = GetStateList() });
}
[HttpPost]
public ActionResult MyAction(MyModel model)
{
if (!ModelState.IsValid)
{
model.States = GetStateList();
return View(model);
}
//do whatever you are going to do with the posted information.
return RedirectToAction("<some view>"); //redirect after a post usually to Index.
}
private static IEnumerable<SelectListItem> GetStatesList()
{
var states = new List<SelectListItem>();
foreach (var s in UsStates.ToList())
{
var state = new SelectListItem { Value = s.Abbreviation, Text = s.Name, Disabled = !s.Available };
states.Add(state);
}
return states;
}
}
After looking into the POSTed data, I realized that the submitted form data -States=AL-type is string, which would fail to be set as UsState type.
I changed the model to the following:
public class MyModel
{
public string States { get; set; }
}
I get the string value of the submitted form, at least. Not sure if this is given - if that's the case, I suppose I should use a separate ViewModel in order to get States as a string, and convert that into MyModel where I cast string States into UsState States.
When I run the code, I can only select one item at a time, that's weird because 'ListBoxFor()' is used to select multiple items, so what i want is:
Select multiple items
View (Index.cshtml):
<div>
#Html.ListBoxFor(m => m.DropDownItems, new MultiSelectList(Repository.DDFetchItems(), "Value", "Text", Model.DropDownItems))
</div>
Model (ModelVariables.cs):
public class ModelVariables
{
public List<SelectListItem> DropDownItems { get; set; }
}
public static class Repository
{
public static List<SelectListItem> DDFetchItems()
{
return new List<SelectListItem>()
{
new SelectListItem(){ Text = "Dogs", Value = "1", Selected = true},
new SelectListItem(){ Text = "Cats", Value = "2"},
new SelectListItem(){ Text = "Death", Value = "3"}
};
}
}
Controller (HomeController.cs):
[HttpGet]
public ActionResult Index()
{
ModelVariables model = new ModelVariables()
{
DropDownItems = Repository.DDFetchItems()
};
return View(model);
}
You cannot bind a <select multiple> to a collection of complex objects (which is what List<SelectListItem> is). A <select multiple> posts back an array of simple values (in your case, if you select the 1st and 3rd options, it will submit [1, 3] (the values of the selected options).
Your model needs a IEnumerable<int> property to bind to.
public class ModelVariables
{
public IEnumerable<int> SelectedItems { get; set; }
public IEnumerable<SelectListItem> DropDownItems { get; set; }
}
and then in the GET method
public ActionResult Index()
{
var ModelVariables= new ModelVariables()
{
DropDownItems = Repository.DDFetchItems(),
SelectedItems = new List<int>(){ 1, 3 } // to preselect the 1st and 3rd options
};
return View(model);
}
and in the view
#Html.ListBoxFor(m => m.SelectedItems, Model.DropDownItems)
Side notes
Remove Selected = true in the DDFetchItems() method - its
ignored by the ListBoxFor() method because its the value of the
property your binding to which determines what is selected
There is not need to build a new identical SelectList from the
first one inside the ListBoxFor() method (property DropDownItems
is already IEumerable<SelectListItem>)
Need some advice I am trying to write a validator which only fires when a specific value in a dropdownlist is selected.
I have two drop down lists on this form one for Countries and the other for US States, the States dropdownlist only shows when the United States is selected from the Countries dropdownlist.
I need a validator that makes the State dropdownlist list a required field only if the United States is selected as a country.
As background information this a MVC3 Web Application and the show/hide code for the States dropdownlist is JQuery.
Another alternative is to dynamically add the rule to jQuery for validation.
You would need to check on the server side this custom logic as well however.
You can do this in your controller, or ideally your VieWModel would implement IValidateableObject to check if country="usa" then county is required.
Use jQuery's .rules.add and .remove:
http://docs.jquery.com/Plugins/Validation/rules#.22remove.22rules
So you could do something along the lines of:
$(document).ready(function() {
$("#country").change(function(){
if($(this).val()=="usa")
{
$("#yourCountyDropDown").rules("add", {
required: true,
messages: {
required: "County is required"
}
});
}
else
{
$("#yourCountyDropDown").rules("remove");
}
});
});
and for your ViewModel
public class WhateverYourObjectNameCreateViewModel : IValidatableObject
{
#region Validation
public IEnumerable Validate(ValidationContext validationContext)
{
if (this.Country=="USA" && string.IsNullOrEmpty(this.County))
{
yield return new ValidationResult("County is required");
}
}
#endregion
}
You could write a custom validation attribute:
public class RequiredIfAttribute : ValidationAttribute
{
public RequiredIfAttribute(string otherProperty, object otherPropertyValue)
{
OtherProperty = otherProperty;
OtherPropertyValue = otherPropertyValue;
}
public string OtherProperty { get; private set; }
public object OtherPropertyValue { get; private set; }
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var property = validationContext.ObjectType.GetProperty(OtherProperty);
if (property == null)
{
return new ValidationResult(string.Format("Unknown property: {0}", OtherProperty));
}
object otherPropertyValue = property.GetValue(validationContext.ObjectInstance, null);
if (!object.Equals(OtherPropertyValue, otherPropertyValue))
{
return null;
}
if (value != null)
{
return null;
}
return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
}
}
Now you could have a view model:
public class MyViewModel
{
public string Country { get; set; }
[RequiredIf("Country", "usa", ErrorMessage = "Please select a state")]
public string State { get; set; }
public IEnumerable<SelectListItem> Countries
{
get
{
return new[]
{
new SelectListItem { Value = "fr", Text = "France" },
new SelectListItem { Value = "usa", Text = "United States" },
new SelectListItem { Value = "spa", Text = "Spain" },
};
}
}
public IEnumerable<SelectListItem> States
{
get
{
return new[]
{
new SelectListItem { Value = "al", Text = "Alabama" },
new SelectListItem { Value = "ak", Text = "Alaska" },
new SelectListItem { Value = "az", Text = "Arizona" },
};
}
}
}
a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
and a view:
#model MyViewModel
#using (Html.BeginForm())
{
<div>
#Html.DropDownListFor(x => x.Country, Model.Countries, "-- Country --")
#Html.ValidationMessageFor(x => x.Country)
</div>
<div>
#Html.DropDownListFor(x => x.State, Model.States, "-- State --")
#Html.ValidationMessageFor(x => x.State)
</div>
<button type="submit">OK</button>
}
You might also find the Foolproof package of validation attributes useful.
I have a page that has two drop down lists and based upon the selection of these two lists I would like to populate a textarea with some data on submit button press.
The behavior that I am seeing while debugging is that the page is rendered, I make my selections and press submit. The DataAccess returns the correct results and the View returns, but with an exception "There is no ViewData item of type 'IEnumerable' that has the key 'People'.
I can see that I could re-setup the drop down lists, but it feels like I'm approaching this incorrectly. Is there another approach for doing this sort of action in MVC 3?
public ActionResult Test()
{
//People for dropdownlist 1
var db = peopleRepository.People;
var query = db.Select(c => new {c.Id, c.Name});
ViewBag.People = new SelectList(query.AsEnumerable(), "Id", "Name");
//Elements for dropdownlist 2
var list = new Dictionary<string, string> {{"1", "Name"}, {"2", "Address"}, {"3", "Zip"}};
ViewBag.Elements = new SelectList(list, "Key", "Value");
return View();
}
// This part is what I'm confused about.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Test(string people, string elements)
{
if (ModelState.IsValid)
{
// Output from persistent storage query
var da = new DatabaseAccess(people, elements);
ViewBag.Results = da.Execute();
}
return View();
}
View:
#using (Html.BeginForm("Test", "Home", FormMethod.Post))
{
#Html.DropDownList("People", (SelectList)ViewBag.People, "--Select One--")
#Html.DropDownList("Elements", (SelectList)ViewBag.Elements, "--Select One--")
#Html.TextArea("Results", (string)ViewBag.Results, 10, 120, "")
}
Here is how I would quickly construct it :
Model :
public class People
{
public int Id { get; set; }
public string Name { get; set; }
}
ViewModel (everything needed by the view):
public class TestViewModel
{
public int SelectedPeopleId { get; set; }
public string SelectedElementId { get; set; }
public SelectList People { get; set; }
public SelectList Elements { get; set; }
public String Results { get; set; }
}
Controller (used Index as the default Action, create an init function for the view model that can be adapted)to anything more appropriate :
public class HomeController : Controller
{
private static TestViewModel InitTestVM()
{
//People for dropdownlist 1
var db = new List<People>();//peopleRepository.People;
db.Add(new People { Id = 1, Name = "Name 1" });
db.Add(new People { Id = 2, Name = "Name 2" });
var query = db.Select(c => new { c.Id, c.Name });
//Elements for dropdownlist 2
var list = new Dictionary<string, string> { { "1", "Name" }, { "2", "Address" }, { "3", "Zip" } };
TestViewModel testVM = new TestViewModel
{
People = new SelectList(query.AsEnumerable(), "Id", "Name"),
Elements = new SelectList(list, "Key", "Value")
};
return testVM;
}
public ActionResult Index()
{
return View(InitTestVM());
}
// This part is what I'm confused about.
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(TestViewModel testVM)
{
var vm = InitTestVM();
if (ModelState.IsValid && testVM != null)
{
ModelState.Clear();
// Output from persistent storage query
//var da = new DatabaseAccess(people, elements);
vm.Results = "sfdfsdfsdfsdfsdfsdfsdfsdf";//da.Execute();
vm.SelectedElementId = testVM.SelectedElementId;
vm.SelectedPeopleId = testVM.SelectedPeopleId;
return View(vm);
}
return View(vm);
}
}
And finally the View :
#model ViewModels.TestViewModel
#using (Html.BeginForm("Index", "Home", FormMethod.Post))
{
#Html.DropDownListFor(m => m.SelectedPeopleId, Model.People, "--Select One--")
#Html.DropDownListFor(m => m.SelectedElementId, Model.Elements, "--Select One--")
#Html.TextAreaFor(m => m.Results, 10, 120, "")
<input type="submit" value="Test" />
}