#Html.DropDownList unable to add form-control class - asp.net-mvc

I need to add a class to a DropDownList so it looks more presentable. Therefore, I'm using the code below with htmlAttribute:
#Html.DropDownList("DepartmentId", "Select a Department:", htmlAttributes: new { #class = "form-control" })
I'm getting errors because it says:
does not contain a definition for 'DropDownList' and the best extension method overload 'System.Web.Mvc.Html.SelectExtensions.DropDownList(System.Web.Mvc.HtmlHelper, string, System.Collections.Generic.IEnumerable<System.Web.Mvc.SelectListItem>, string)' has some invalid arguments
Can someone teach me how to add form-control class to do DropDownList?
Code that is working:
#Html.DropDownList("DepartmentId", null, htmlAttributes: new { #class = "form-control" })

There is no overload of DropDownList that lets you specify both a default option as well as HTML attributes. DropDownListFor does, however.
How are you populating your dropdown list with options? If you are doing it server-side in your controller action, you can (and probably should) use DropDownListFor:
#model DepartmentViewModel
...
#Html.DropDownListFor(model => model.DepartmentId, Model.Departments, "Select a Department:", new { #class = "form-control" })
Your view model class would be like:
public class DepartmentViewModel {
...
public int DepartmentId { get; set; }
public IEnumerable<SelectListItem> Departments { get; set; }
...
}
And in your controller action:
public ActionResult Index() {
...
var model = new DepartmentViewModel();
model.Departments = new List<SelectListItem> {
new SelectListItem { Value = "1", Text = "First Department"},
new SelectListItem { Value = "2", Text = "Second Department"}
};
...
return View(model);
}
But if you're populating the dropdown list with values via javascript/jquery, it would be just as easy to use regular HTML syntax:
<select name="DepartmentId" id="DepartmentId" class="form-control">
<option value="">Select a Department:</option>
<select>

Related

Set The selected Value of a Multiselect from Controller to View after submitting an invalid Model

I am using a Multiselect DropDownList to generate a multiple <select>
I was able to generate it and was working fine.
But If I try to submit it using the parameters:
Name = null
ObjAOption = [1,2] // assume I selected 2 options in my multiselect
ObjAOption will just select option value='1' instead of select options 1, and 2.
Is there any way I can get back the selected options and pass it back to my view by setting it in my controller? I would love to use HTML helper and not to use jQuery or javascript on this part.
Controller:
public ActionResult AddObjectA(AddModel am){
if(ModelState.IsValid){
//Save
}
else {
am.ObjA = // new List of ObjectA with atleast 4 option
return View("MyView",am);
}
}
View:
#Html.LabelFor(model => model.ObjA, "Object A")
#Html.DropDownList("ObjAOption", new MultiSelectList(Model.ObjA, "Key", "Name"), "-- Select Object A--", new { #class = "custom-select custom-select-sm", multiple="" })
#Html.ValidationMessageFor(m => m.ObjAOption, "", new { #class = "text-danger" })
Model:
public class AddModel {
[Required]
public String Name {get;set;}
public IEnumerable<ObjectA> ObjA{ get; set; }
[Required(ErrorMessage = "Please select at least one option")]
public List<int>ObjAOption{ get; set; }
}
public class ObjectA {
public int Key {get;set;}
public string Name {get;set;}
}
Have you tried to use the helper Hiddenfor ? It generate a field that keep your element value, name and attribute :
View:
#Html.LabelFor(model => model.ObjA, "Object A")
#Html.DropDownList("ObjAOption", new MultiSelectList(Model.ObjA, "Key", "Name"), "-- Select Object A--", new { #class = "custom-select custom-select-sm", multiple="" })
#Html.ValidationMessageFor(m => m.ObjAOption, "", new { #class = "text-danger" })
#Html.HiddenFor(m => m.ObjAOption)
Solution:
I scrapped out my DropDownList and tried using ListBoxFor as discussed here

Can not retrieve DropdownList value in model using mvc & ado.net

I am sending selectedlist to view using ViewBag. Here is the get method that i pass through ViewBag
public List<Dept> GetDept()
{
connection();
List<Dept> deptList = new List<Dept>();
SqlCommand com = new SqlCommand("Sp_GetDept", con);
com.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(com);
DataTable dt = new DataTable();
con.Open();
da.Fill(dt);
con.Close();
//Bind EmpModel generic list using dataRow
foreach (DataRow dr in dt.Rows)
{
deptList.Add(
new Dept
{
DeptId = Convert.ToInt32(dr["DeptId"]),
Name = Convert.ToString(dr["Name"])
}
);
}
return deptList;
}
public ActionResult Create()
{
DeptRepo repo = new DeptRepo();
ViewBag.Dept = new SelectList(repo.GetDept(), "DeptId", "Name");
return View();
}
View Code:
<div class="form-group">
#Html.LabelFor(model => model.Dept, "Department", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownList("Dept", null, "--Select--", htmlAttributes: new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.Dept, "", new { #class = "text-danger" })
</div>
</div>
Student model:
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public string Roll { get; set; }
public int DeptId { get; set; }
public virtual Dept Dept { get; set; }
}
post method:
[HttpPost]
public ActionResult Create(Student std)
{
try
{
if (ModelState.IsValid)
{
StudentRepo repo = new StudentRepo();
repo.AddStudent(std);
}
return RedirectToAction("Index");
}
catch
{
return View();
}
}
In post method dropdownlist id value found null in student object.
Can anybody tell me how i can retrieve foreignkey Id using mvc and ado.net.
Any kind of help would be greatly appreciated.
Your current code,
#Html.DropDownList("Dept", null, "--Select--",
htmlAttributes: new { #class = "form-control" })
will generate HTML markup for a SELECT element with name attribute value set to Dept
<select class="form-control" id="Dept" name="Dept">
<option value="">--Select--</option>
</select>
Since you are using the Student class as your httppost action method parameter, for model binding to properly map the selected option value to DeptId property of the Student object, you need to make sure that your select element name is also DeptId
If your view is strongly typed to the Student class, you can use DropDownListFor helper method
#Html.DropDownListFor(a => a.DeptId, ViewBag.Dept as IEnumerable<SelectListItem>,
"--Select--", htmlAttributes: new { #class = "form-control" })
Or
You can use DropDownList method and give DeptId as the first param (name of the control) and explicitly specify the collection to use for building the options as the second param.
#Html.DropDownList("DeptId", ViewBag.Dept as IEnumerable<SelectListItem>,
"--Select--", htmlAttributes: new { #class = "form-control" })
This will render the SELECT element with name attribute value set to DeptId and when the form is submitted, model binder will be able to use the selected option value to set it to the DeptId property of the Student object(which is your httppost action method parameter)

Issue with Model Binding

I have created a View Model called CompetitionRoundModel which is partially produced below:
public class CompetitionRoundModel
{
public IEnumerable<SelectListItem> CategoryValues
{
get
{
return Enumerable
.Range(0, Categories.Count())
.Select(x => new SelectListItem
{
Value = Categories.ElementAt(x).Id.ToString(),
Text = Categories.ElementAt(x).Name
});
}
}
[Display(Name = "Category")]
public int CategoryId { get; set; }
public IEnumerable<Category> Categories { get; set; }
// Other parameters
}
I have structured the model this way because I need to populate a dropdown based on the value stored in CategoryValues. So for my view I have:
#using (Html.BeginForm())
{
<div class="form-group">
#Html.LabelFor(model => model.CategoryId, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.DropDownListFor(model => model.CategoryId, Model.CategoryValues, new { #class = "form-control" })
#Html.ValidationMessageFor(model => model.CategoryId, "", new { #class = "text-danger" })
</div>
</div>
// Other code goes here
}
I have selected model.CategoryId in the DropDownListFor() method since I want to bind the selected value to CategoryId. I really don't care for CategoryValues, I just need it to populate the DropDown.
My problem now is that when my Controller receives the values for my Model in the action method, CategoryValues is null which causes the system to throw a ArgumentNullException (the line that is highlighted is the return Enumerable line.
I have even tried [Bind(Exclude="CategoryValues")] but no change at all. Any help would be much appreciated.
Your not (and should not be) creating form controls for each property of each Category in your IEnumerable<Category> collection so in your POST method, the value of Categories is null (it never gets initialized). As soon as you attempt CategoryValues and exception is thrown by your .Range(0, Categories.Count()) line of code in the getter.
Change you view model to give CategoryValues a simple geter/setter, and delete the Categories property
public class CompetitionRoundModel
{
public IEnumerable<SelectListItem> CategoryValues { get; set; }
[Display(Name = "Category")]
public int CategoryId { get; set; }
.... // Other properties
}
and populate the SelectList in the controller methods, for example
var categories db.Categories; // your database call
CompetitionRoundModel model = new CompetitionRoundModel()
{
CategoryValues = categories.Select(x => new SelectListItem()
{
Value = x.Id.ToString(),
Text = x.Name
},
....
};
return View(model);
or alternatively
CompetitionRoundModel model = new CompetitionRoundModel()
{
CategoryValues = new SelectList(categories, "Id", "Name" ),
Note also that if you return the view (because ModelState is invalid, the you need to repopulate the value of CategoryValues (refer The ViewData item that has the key 'XXX' is of type 'System.Int32' but must be of type 'IEnumerable' for more detail)
Since CategoryValues just populates the drop down, it will never post back to the server and you'll need to rebuild the list from the database before using it in the GET or POST operation. The CategoryId property is the value that will be posted back to the server from the DropDownList.

How to add "required" attribute to mvc razor viewmodel text input editor

I have the following MVC 5 Razor HTML helper:
#Html.TextBoxFor(m => m.ShortName,
new { #class = "form-control", #placeholder = "short name"})
I need this field to be required (i.e. have a red outline when user navigates out without putting a value inn). In a WebForms HTML 5 I could just say <input type="text" required /> to have this effect.
What is the proper syntax to accomplish this in a Razor syntax?
You can use the required html attribute if you want:
#Html.TextBoxFor(m => m.ShortName,
new { #class = "form-control", placeholder = "short name", required="required"})
or you can use the RequiredAttribute class in .Net. With jQuery the RequiredAttribute can Validate on the front end and server side. If you want to go the MVC route, I'd suggest reading Data annotations MVC3 Required attribute.
OR
You can get really advanced:
#{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = new Dictionary<string, object>(
Html.GetUnobtrusiveValidationAttributes(ViewData.TemplateInfo.HtmlFieldPrefix));
attributes.Add("class", "form-control");
attributes.Add("placeholder", "short name");
if (ViewData.ModelMetadata.ContainerType
.GetProperty(ViewData.ModelMetadata.PropertyName)
.GetCustomAttributes(typeof(RequiredAttribute), true)
.Select(a => a as RequiredAttribute)
.Any(a => a != null))
{
attributes.Add("required", "required");
}
#Html.TextBoxFor(m => m.ShortName, attributes)
}
or if you need it for multiple editor templates:
public static class ViewPageExtensions
{
public static IDictionary<string, object> GetAttributes(this WebViewPage instance)
{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = new Dictionary<string, object>(
instance.Html.GetUnobtrusiveValidationAttributes(
instance.ViewData.TemplateInfo.HtmlFieldPrefix));
if (ViewData.ModelMetadata.ContainerType
.GetProperty(ViewData.ModelMetadata.PropertyName)
.GetCustomAttributes(typeof(RequiredAttribute), true)
.Select(a => a as RequiredAttribute)
.Any(a => a != null))
{
attributes.Add("required", "required");
}
}
}
then in your templates:
#{
// if you aren't using UnobtrusiveValidation, don't pass anything to this constructor
var attributes = this.GetAttributes();
attributes.Add("class", "form-control");
attributes.Add("placeholder", "short name");
#Html.TextBoxFor(m => m.ShortName, attributes)
}
Update 1 (for Tomas who is unfamilar with ViewData).
What's the difference between ViewData and ViewBag?
Excerpt:
So basically it (ViewBag) replaces magic strings:
ViewData["Foo"]
with magic properties:
ViewBag.Foo
On your model class decorate that property with [Required] attribute. I.e.:
[Required]
public string ShortName {get; set;}
A newer way to do this in .NET Core is with TagHelpers.
https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro
Building on these examples (MaxLength, Label), you can extend the existing TagHelper to suit your needs.
RequiredTagHelper.cs
using Microsoft.AspNetCore.Razor.TagHelpers;
using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System.Linq;
namespace ProjectName.TagHelpers
{
[HtmlTargetElement("input", Attributes = "asp-for")]
public class RequiredTagHelper : TagHelper
{
public override int Order
{
get { return int.MaxValue; }
}
[HtmlAttributeName("asp-for")]
public ModelExpression For { get; set; }
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
if (context.AllAttributes["required"] == null)
{
var isRequired = For.ModelExplorer.Metadata.ValidatorMetadata.Any(a => a is RequiredAttribute);
if (isRequired)
{
var requiredAttribute = new TagHelperAttribute("required");
output.Attributes.Add(requiredAttribute);
}
}
}
}
}
You'll then need to add it to be used in your views:
_ViewImports.cshtml
#using ProjectName
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
#addTagHelper "*, ProjectName"
Given the following model:
Foo.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace ProjectName.Models
{
public class Foo
{
public int Id { get; set; }
[Required]
[Display(Name = "Full Name")]
public string Name { get; set; }
}
}
and view (snippet):
New.cshtml
<label asp-for="Name"></label>
<input asp-for="Name"/>
Will result in this HTML:
<label for="Name">Full Name</label>
<input required type="text" data-val="true" data-val-required="The Full Name field is required." id="Name" name="Name" value=""/>
I hope this is helpful to anyone with same question but using .NET Core.
I needed the "required" HTML5 atribute, so I did something like this:
<%: Html.TextBoxFor(model => model.Name, new { #required = true })%>
#Erik's answer didn't fly for me.
Following did:
#Html.TextBoxFor(m => m.ShortName, new { data_val_required = "You need me" })
plus doing this manually under field I had to add error message container
#Html.ValidationMessageFor(m => m.ShortName, null, new { #class = "field-validation-error", data_valmsg_for = "ShortName" })
Hope this saves you some time.

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