Best programming practice of using DropDownList in ASP.Net MVC - c#

I'm working with MVC 5 for a few months read a lot of articles, forums and documentation but always wondering what is better in the view;
1) binding data using static method of model like here
2) binding the same data using ViewData[index] which is set in Controller that with previous example will look like this
#Html.DropDownListFor(n => n.MyColorId, ViewData[index])

You want to use option 1, mainly because you want to use Strongly Type as much as possible, and fix the error at compile time.
In contrast, ViewData and ViewBag are dynamic, and compile could not catch error until run-time.
Here is the sample code I used in many applications -
Model
public class SampleModel
{
public string SelectedColorId { get; set; }
public IList<SelectListItem> AvailableColors { get; set; }
public SampleModel()
{
AvailableColors = new List<SelectListItem>();
}
}
View
#model DemoMvc.Models.SampleModel
#using (Html.BeginForm("Index", "Home"))
{
#Html.DropDownListFor(m => m.SelectedColorId, Model.AvailableColors)
<input type="submit" value="Submit"/>
}
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new SampleModel
{
AvailableColors = GetColorListItems()
};
return View(model);
}
[HttpPost]
public ActionResult Index(SampleModel model)
{
if (ModelState.IsValid)
{
var colorId = model.SelectedColorId;
return View("Success");
}
// If we got this far, something failed, redisplay form
// ** IMPORTANT : Fill AvailableColors again; otherwise, DropDownList will be blank. **
model.AvailableColors = GetColorListItems();
return View(model);
}
private IList<SelectListItem> GetColorListItems()
{
// This could be from database.
return new List<SelectListItem>
{
new SelectListItem {Text = "Orange", Value = "1"},
new SelectListItem {Text = "Red", Value = "2"}
};
}
}

I would say, completely separate dropdown items from ViewData. Have your model contain a property for dropdown. Fill that in your controller and just bind it in the view like
ViewModel
class MyModel
{
public IEnumerable<SelectListItem> dropdowndata {get; set;}
}
Controller
public Actionresult MyAction(string id)
{
IEnumerable<data> mydata = callDALmethodtogetit();
Mymodel model = new MyModel
{
dropdowndata = mydata.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.Name
});
}
}
View
#Html.DropDownListFor(model => model.dropdowndata, Model.dropdowndata)

Related

.net core MVC tries validate IEnumerable options for dropdown, but Required attr is only "expression"

I have an issue with model validation in webapp (.NET core MVC) - in form there are few dropdown lists, these DDLs have as expression required non-nullable int parameter and SelectListItem option list. I am getting message the SelectListItem option lists are required (the int parameter pass as valid). What is wrong or what I missed?
Model (MyModel f.e.):
[Required]
[Display(Name = nameof(MyResources.Day), ResourceType = typeof(MyResources))]
public int Day { get; set; }
public IEnumerable<SelectListItem> Days { get; set; }
Controller, GET method
[HttpGet]
public IActionResult Index() {
var model = MyModel();
FillModelOptions(model);
return View(model);
}
[HttpPost]
public IActionResult Index(MyModel model) {
if (ModelState.IsValid) {
*save form, go somewhere*
}
FillModelOptions(model);
return View(model);
}
private void FillModelOptions(MyModel model) {
var days = new List<SelectListItem>();
for (var i = 1; i < 32; i++) {
days.Add(new SelectListItem() { Text = i.ToString(), Value = i.ToString() });
}
model.Days = days.AsEnumerable();
}
In view I have #Html.ValidationSummary(false, "") and this returns me hidden error: The Days field is required. (but the Days is IEnumerable, and Day is not empty)
View:
#using (Html.BeginForm("Index", "MyController", FormMethod.Post) {
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(false, "") #*after post getting The Days field is required*#
<div class="somebootstrapclasses">
#Html.DropDownListFor(model => model.Day, Model.Days, "Select...", new {#class="form-select", #aria_label="Select..."})
#Html.LabelFor(model => model.Day)
#Html.ValidationMessageFor(model => model.Day)
</div>
}
You write the property like this in MyModel :
public IEnumerable<SelectListItem> Days { get; set; }
The Days property is non-nullable(Equivalent to hermit adding a [Required] property) by default, So if you don't bind any value to this property, ModelState.IsValid will return false.
If you don't want this, you can disable this by deleting the below line from the csproj file or setting it as disable. By default(.Net 6) value is enable.
<Nullable>enable</Nullable>
Or, you can just add ? to make it nullable:
public IEnumerable<SelectListItem>? Days { get; set; }
Or, make ModelState ignore this property:
if (ModelState.Remove("Days"))
{
*save form, go somewhere*
}

Submit model with SelectList in ASP.NET MVC

What I have is a form with multiple inputs that I want to use to query database for some results. Form has some default values and it all works, however I have problem submitting it to itself.
The returned error is "No paramaterless constructor defined for this object" and it is caused by SelectList object.
I have tried this solution and made psUserType private with getter and setter and intialized it as empty list, but then my dropDown menu had no values on start. Not sure why GetUserTypes hadn't filled them.
What am I doing wrong here? How does one have both preselected values and also send the same model with user-selected values, while also displaying results on the same page?
Does it make sense to use the same model for all 3 actions: 1. display form and inputs with default values 2. post selected values during submit 3. return results and selected values? I've read this solution also but not sure how to use 2 or 3 separate models here.
Any help is appreciated. Thanks in advance.
Model
public class SearchDownloadsModel
{
public SelectList psUserType { get; private set; } //causes problem on submit
public string psText { get; set; }
public MultiSelectList psColumns { get; private set; }
public IEnumerable<ResultsRowModel> psResults { get; set; }
public SearchDownloadsModel()
{
this.psUserType = GetUserTypes();
this.psColumns = GetColumns();
this.psResults = new List<ResultsRowModel>(); //empty by default
}
public SelectList GetUserTypes()
{
List<SelectListItem> items = new List<SelectListItem>()
{
new SelectListItem { Value="user", Text="Single User" },
new SelectListItem { Value="group", Text="User group" },
...
};
return new SelectList(items, "Value", "Text");
}
public MultiSelectList GetColumns()
{
List<SelectListItem> items = new List<SelectListItem>()
{
new SelectListItem { Value = "user", Text="Username" },
new SelectListItem { Value = "file", Text="Filename" },
new SelectListItem { Value = "titl", Text="Title" },
new SelectListItem { Value = "auth", Text="Author" },
...
};
return new MultiSelectList(items, "Value", "Text");
}
}
public class ResultsRowModel
{
public int ID { get; set; }
public string EventTime { get; set; }
public string FileName { get; set; }
public string FilePath { get; set; }
public string UserName { get; set; }
...
}
View
#model Proj.Models.SearchDownloadsModel
#using (Html.BeginForm("Downloads", "Home", FormMethod.Post))
{
#Html.DropDownListFor(x => x.psUserType, Model.psUserType)
#Html.TextBoxFor(x => x.psText)
#Html.ListBoxFor(x => x.psColumnsSelected, Model.psColumns, new { multiple = "multiple" })
<button type="submit" class="btn btn-primary">Search</button>
}
#if (Model.psResults != null && Model.psResults.Any())
{
<table>
<tr>
<th>User</th>
<th>File</th>
</tr>
#foreach (var row in Model.psResults)
{
<tr>
<td>#row.UserName</td>
<td>#row.FileName</td>
</tr>
}
</table>
}
Controller
[HttpGet]
public ActionResult Downloads()
{
SearchDownloadsModel model = new SearchDownloadsModel();
model.psColumnsSelected = new List<string>() { "user", "file" }; //preselected values
return View(model);
}
[HttpPost]
public ActionResult Downloads(SearchDownloadsModel model)
{
model.psResults = queryDatabase(model);
return View(model);
}
private List<ResultsRowModel> queryDatabase(SearchDownloadsModel model)
{
//...
}
EDIT: Added ResultsRowModel under SearchDownloadsModel
In ASP.NET MVC you should only put variables containing the posted or selected values in the ViewModel class. Select List items are considered extra info and are typically passed from the Action Method into the View (.cshtml) using ViewBag items.
Many of the rendering extension methods are even written specifically for such an approach, leading to code such as this:
Controller
ViewBag.PersonID = persons.ToSelectList(); // generate SelectList here
View
#Html.DropDownListFor(model => model.PersonID)
#* The above will look for ViewBag.PersonID, based on the name of the model item *#
The DropDownListFor generates a <select> element with the name of the property you bind it to. When you submit the form, that name will be included as one of the form fields and its value will be the option's value you select.
You're binding the DropDownList to a property of type SelectList (psUserType) and when your action is called, a new instance of SelectList must be created in order to bind the form field to it. First of all, the SelectList class does not have a parameterless constructor and, thus, your error. Secondly, even if a SelectList could be created as part of model binding, the <select> element is submitting a string value which wouldn't be convertible to SelectList anyways.
What you need to do is to add a string property to your SearchDownloadsModel, for example:
public string SelectedUserType { get; set; }
Then bind the dropdownlist to this property:
#Html.DropDownListFor(x => x.SelectedUserType, Model.psUserType)
When you submit the form, this new property will have the value you selected in the drop down.
Peter's answer and Stephen's comments helped me solve the problem.
Pehaps someone will find it useful.
Any further suggestions always welcome.
Model
public class PobraniaSzukajModel
{
public IEnumerable<SelectListItem> UserTypes { get; set; }
public string psSelectedUserType { get; set; }
public IEnumerable<SelectListItem> Columns { get; set; }
public IEnumerable<string> psSelectedColumns { get; set; }
public string psText { get; set; }
public ResultsModel psResults { get; set; }
}
View
#Html.ListBoxFor(x => x.psSelectedUserType, Model.Columns)
#Html.TextBoxFor(x => x.psText)
#Html.ListBoxFor(x => x.psSelectedColumns, Model.Columns)
Controller
[HttpGet]
public ActionResult Downloads()
{
SearchDownloadsModelmodel = new SearchDownloadsModel();
model.UserTypes = GetUserTypes();
model.Columns = GetColumns();
model.psColumnsSelected = new List<string>() { "user", "file" }; //preselected values
return View(model);
}
[HttpPost]
public ActionResult Downloads(SearchDownloadsModel model)
{
model.UserTypes = GetUserTypes();
model.Columns = GetColumns();
model.psResults = GetResults(model);
return View(model);
}
public SelectList GetUserTypes()
{
//...
}
public MultiSelectList GetColumns()
{
//...
}
public ResultsModel GetResults()
{
//...
}

Pass chosen value with BeginForm and DropDownList

So, I am trying to pass the chosen multiselect values throught a DropDownList and a BeginForm. Don't want to pass with javascript/ajax. The chosen plugin is working fine, show me the entries like i want. But I'm getting null values on controller:
Model
public class SorteioEspecial
{
RepositoryService service = new RepositoryService();
public SorteioEspecial()
{
funcionario = new List<Funcionario>();
ponderacaoFuncionario = new List<PonderacaoFuncionario>();
SelectedIds = new List<int>();
}
public int Id { get; set; }
public IEnumerable<Funcionario> funcionario { get; set; }
public IEnumerable<PonderacaoFuncionario> ponderacaoFuncionario { get; set; }
public List<int> SelectedIds { get; set; }
public IEnumerable<Funcionario> GetFuncionarios()
{
funcionario = service.GetFuncionarios();
return funcionario;
}
public IEnumerable<PonderacaoFuncionario> GetPonderacaoFuncionario()
{
ponderacaoFuncionario = service.GetPonderacaoFuncionario();
return ponderacaoFuncionario;
}
}
Controller
[HttpGet]
public ActionResult EscolherFuncionarios()
{
var sorteioEspecial = new SorteioEspecial();
List<Funcionario> list = new List<Funcionario>();
list = sorteioEspecial.GetFuncionarios().ToList().OrderBy(x => x.Nome).ToList();
ViewBag.FuncionarioId = new SelectList(list, "Id", "Nome");
return View(sorteioEspecial);
}
[HttpPost]
public ActionResult EscolherFuncionarios(List<int> SelectedIds)
{
return View();
}
View
#model Apdd.Models.SorteioEspecial
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Escolha os funcionários a ir a sorteio</h2>
#using (Html.BeginForm())
{
#Html.DropDownList("FuncionarioId", null, htmlAttributes: new { #class = "chosen-select", #data_placeholder = "Pick one!", #multiple = "true" })
<input type="submit" value="save" />
}
<script src="~/Scripts/jquery-2.1.1.js"></script>
<script src="~/Scripts/jquery.js"></script>
<script src="~/Scripts/chosen.proto.js"></script>
<link href="~/Scripts/chosen.css" rel="stylesheet" />
<script src="~/Scripts/chosen.jquery.js"></script>
<script>
$(".chosen-select").chosen({
disable_search_threshold: 10,
no_results_text: "None!",
width: "95%"
});
</script>
The values in the ViewBag are a list of entities (Id, Name, and some more parameters), and for what I have been seing, the chose only passes the Id, exactly What I want. What I have to do to pass the values to controller?
First of all the selected items will be posted as comma seperated string, so you need to bind your dropdownlist with a string property.
Secondly your dropdownlist name is FuncionarioId so it will post in that key of FormCollection while you have parameter in your action which of type List<int>
If you change your action signatures and check in form collection, you will find the comma seperated values in FormCollection:
[HttpPost]
public ActionResult EscolherFuncionarios(FormCollection form)
{
string selectedValues = form["FuncionarioId"];
return View();
}
You can also refer to this article of Rick Anderson where he explained how to use Multi Select
The values are posted to the controller, bot the problem is with controller parameter binding, because the names from the parameter and the drop down list is different. If you change the name of the controller parameter list from "SelectedIds" to drop down list name "FuncionarioId" it should work.
So change this:
[HttpPost]
public ActionResult EscolherFuncionarios(List<int> SelectedIds)
{
return View();
}
to this:
[HttpPost]
public ActionResult Test(List<int> FuncionarioId)
{
return View();
}

Unable to get new selected value of #Html.DropDownListFor in case of using autobinding

Unable to bind model with value from #Html.DropDownListFor field in MVC3 (razor) of a strongly typed view.
Model used for strongly typed view:
public class MyModel
{
public string Name{get;set;}
pulic int Status_ID{get;set;}
}
In strongly typed view:
#Html.DropDownListFor(m=> m.Status_ID, new SelectList(Repo.AllStatus, "ID", Name"), new {#style = "width: 100%;" })
Before submitting the form I selected the option with ID=24(i.e. value=24 option is selected)
In controller
public ActionResult AddMyModel(MyModel myModel)
{
}
While debugging, in controller, I got that:
myModel.Name is expected value but
myModel.Status_ID is 0 not 24
where am I going wrong??
You need to pass in a view model to your view with all the statuses already populated.
Here is a solution to your problem. Modify it to fit in with your scenario. I hope I haven't left out anything. The code below is what I am assuming your models might look like.
Your status class:
public class Status
{
public int Id { get; set; }
public string Name { get; set; }
}
On your view you need to pass in a view model that contains a list of all your statuses:
public class YourViewModel
{
public int StatusId { get; set; }
public IEnumerable<Status> Statuses { get; set; }
}
Your controller:
public class YourController : Controller
{
private readonly IStatusRepository statusRepository;
public YourController(IStatusRepository statusRepository)
{
this.statusRepository = statusRepository;
}
public ActionResult YourAction()
{
YourViewModel viewModel = new YourViewModel
{
Statuses = statusRepository.FindAll()
};
return View(viewModel);
}
}
And then your view will look something like this:
#model YourProject.ViewModels.Statuses.YourViewModel
#Html.DropDownListFor(
x => x.StatusId,
new SelectList(Model.Statuses, "Id", "Name", Model.StatusId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.StatusId)
I hope this can help you in the right direction and shed some light on what you are trying to achieve.

Mvc SelectList doesn't bind if Dictionary key is nullable decimal

Why is my dropdownlist not binding? Using the DropDownListFor Razor helper function.
View:
#Html.DropDownListFor(m => m.ModelObject.VatRate, Model.VatRatesList)
ViewModel:
public SelectList VatRatesList
{
get
{
return new SelectList(
new Dictionary<decimal, string>
{
{ 0m, string.Empty },
{ 1.2m, "20%" },
{ 1m, "0%" }
}, "Key", "Value",
ModelObject.VatRate ?? 0m);
}
}
Thanks.
UPDATE
On further investigation I have found out that this is something to do with the model property that I am trying to bind. It is a nullable decimal. When I change it to a decimal, the correct value is selected from the list.
Here is where things start to get weird. If I use 4 decimal places for the dictionary keys, it works with a nullable decimal model property. In other words, this works:
public SelectList VatRatesList
{
get
{
return new SelectList(
new Dictionary<decimal, string>
{
{ 0.0000m, string.Empty },
{ 1.2000m, "20%"},
{ 1.0000m, "0%"}
}, "Key", "Value");
}
}
I have no idea why. Perhaps html helper uses ToString() internally. I think ToString() would give a 4dp string representation of the decimal. I'll have to look at the MVC source code to find out.
That exact code works for me (can't see the rest of your model/view so it's fairly difficult to ascertain the problem). This is the code that I used:
Controller:
public ActionResult Index()
{
var model = new Model();
model.ModelObject = new ModelObject();
model.ModelObject.VatRatesList = new SelectList(
new Dictionary<decimal, string>
{
{ 0m, string.Empty },
{ 1.2m, "20%" },
{ 1m, "0%" }
}, "Key", "Value",
model.ModelObject.VatRate ?? 0m);
return View(model);
}
View:
#using (Html.BeginForm())
{
#Html.DropDownListFor(m => m.ModelObject.VatRate, Model.ModelObject.VatRatesList)
<input type="submit" value="Submit me"/>
}
Controller Post Method:
[HttpPost]
public ActionResult Index(Model model)
{
//Breakpointing on the below line, I can see model.ModelObject.VatRate
return RedirectToAction("Index");
}
I used these two classes:
public class Model
{
public ModelObject ModelObject { get; set; }
}
public class ModelObject
{
public decimal? VatRate { get; set; }
public SelectList VatRatesList { get; set; }
}

Categories