Asked  7 Months ago    Answers:  5   Viewed   107 times

I need some help with the select tag helper in ASP.NET Core.

I have a list of employees that I'm trying to bind to a select tag helper. My employees are in a List<Employee> EmployeesList and selected value will go into EmployeeId property. My view model looks like this:

public class MyViewModel
{
   public int EmployeeId { get; set; }
   public string Comments { get; set; }
   public List<Employee> EmployeesList {get; set; }
}

My employee class looks like this:

public class Employee
{
   public int Id { get; set; }
   public string FullName { get; set; }
}

My question is how do I tell my select tag helper to use the Id as the value while displaying FullName in the drop down list?

<select asp-for="EmployeeId" asp-items="???" />

I'd appreciate some help with this. Thanks.

 Answers

74

Using the Select Tag helpers to render a SELECT element

In your GET action, create an object of your view model, load the EmployeeList collection property and send that to the view.

public IActionResult Create()
{
    var vm = new MyViewModel();
    vm.EmployeesList = new List<Employee>
    {
        new Employee { Id = 1, FullName = "Shyju" },
        new Employee { Id = 2, FullName = "Bryan" }
    };
    return View(vm);
}

And in your create view, create a new SelectList object from the EmployeeList property and pass that as value for the asp-items property.

@model MyViewModel
<form asp-controller="Home" asp-action="Create">

    <select asp-for="EmployeeId" 
            asp-items="@(new SelectList(Model.EmployeesList,"Id","FullName"))">
        <option>Please select one</option>
    </select>

    <input type="submit"/>

</form>

And your HttpPost action method to accept the submitted form data.

[HttpPost]
public IActionResult Create(MyViewModel model)
{
   //  check model.EmployeeId 
   //  to do : Save and redirect
}

Or

If your view model has a List<SelectListItem> as the property for your dropdown items.

public class MyViewModel
{
    public int EmployeeId { get; set; }
    public string Comments { get; set; }
    public List<SelectListItem> Employees { set; get; }
}

And in your get action,

public IActionResult Create()
{
    var vm = new MyViewModel();
    vm.Employees = new List<SelectListItem>
    {
        new SelectListItem {Text = "Shyju", Value = "1"},
        new SelectListItem {Text = "Sean", Value = "2"}
    };
    return View(vm);
}

And in the view, you can directly use the Employees property for the asp-items.

@model MyViewModel
<form asp-controller="Home" asp-action="Create">

    <label>Comments</label>
    <input type="text" asp-for="Comments"/>

    <label>Lucky Employee</label>
    <select asp-for="EmployeeId" asp-items="@Model.Employees" >
        <option>Please select one</option>
    </select>

    <input type="submit"/>

</form>

The class SelectListItem belongs to Microsoft.AspNet.Mvc.Rendering namespace.

Make sure you are using an explicit closing tag for the select element. If you use the self closing tag approach, the tag helper will render an empty SELECT element!

The below approach will not work

<select asp-for="EmployeeId" asp-items="@Model.Employees" />

But this will work.

<select asp-for="EmployeeId" asp-items="@Model.Employees"></select>

Getting data from your database table using entity framework

The above examples are using hard coded items for the options. So i thought i will add some sample code to get data using Entity framework as a lot of people use that.

Let's assume your DbContext object has a property called Employees, which is of type DbSet<Employee> where the Employee entity class has an Id and Name property like this

public class Employee
{
   public int Id { set; get; }
   public string Name { set; get; }
}

You can use a LINQ query to get the employees and use the Select method in your LINQ expression to create a list of SelectListItem objects for each employee.

public IActionResult Create()
{
    var vm = new MyViewModel();
    vm.Employees = context.Employees
                          .Select(a => new SelectListItem() {  
                              Value = a.Id.ToString(),
                              Text = a.Name
                          })
                          .ToList();
    return View(vm);
}

Assuming context is your db context object. The view code is same as above.

Using SelectList

Some people prefer to use SelectList class to hold the items needed to render the options.

public class MyViewModel
{
    public int EmployeeId { get; set; }
    public SelectList Employees { set; get; }
}

Now in your GET action, you can use the SelectList constructor to populate the Employees property of the view model. Make sure you are specifying the dataValueField and dataTextField parameters.

public IActionResult Create()
{
    var vm = new MyViewModel();
    vm.Employees = new SelectList(GetEmployees(),"Id","FirstName");
    return View(vm);
}
public IEnumerable<Employee> GetEmployees()
{
    // hard coded list for demo. 
    // You may replace with real data from database to create Employee objects
    return new List<Employee>
    {
        new Employee { Id = 1, FirstName = "Shyju" },
        new Employee { Id = 2, FirstName = "Bryan" }
    };
}

Here I am calling the GetEmployees method to get a list of Employee objects, each with an Id and FirstName property and I use those properties as DataValueField and DataTextField of the SelectList object we created. You can change the hardcoded list to a code which reads data from a database table.

The view code will be same.

<select asp-for="EmployeeId" asp-items="@Model.Employees" >
    <option>Please select one</option>
</select>

Render a SELECT element from a list of strings.

Sometimes you might want to render a select element from a list of strings. In that case, you can use the SelectList constructor which only takes IEnumerable<T>

var vm = new MyViewModel();
var items = new List<string> {"Monday", "Tuesday", "Wednesday"};
vm.Employees = new SelectList(items);
return View(vm);

The view code will be same.

Setting selected options

Some times,you might want to set one option as the default option in the SELECT element (For example, in an edit screen, you want to load the previously saved option value). To do that, you may simply set the EmployeeId property value to the value of the option you want to be selected.

public IActionResult Create()
{
    var vm = new MyViewModel();
    vm.Employees = new List<SelectListItem>
    {
        new SelectListItem {Text = "Shyju", Value = "11"},
        new SelectListItem {Text = "Tom", Value = "12"},
        new SelectListItem {Text = "Jerry", Value = "13"}
    };
    vm.EmployeeId = 12;  // Here you set the value
    return View(vm);
}

This will select the option Tom in the select element when the page is rendered.

Multi select dropdown

If you want to render a multi select dropdown, you can simply change your view model property which you use for asp-for attribute in your view to an array type.

public class MyViewModel
{
    public int[] EmployeeIds { get; set; }
    public List<SelectListItem> Employees { set; get; }
}

This will render the HTML markup for the select element with the multiple attribute which will allow the user to select multiple options.

@model MyViewModel
<select id="EmployeeIds" multiple="multiple" name="EmployeeIds">
    <option>Please select one</option>
    <option value="1">Shyju</option>
    <option value="2">Sean</option>
</select>

Setting selected options in multi select

Similar to single select, set the EmployeeIds property value to the an array of values you want.

public IActionResult Create()
{
    var vm = new MyViewModel();
    vm.Employees = new List<SelectListItem>
    {
        new SelectListItem {Text = "Shyju", Value = "11"},
        new SelectListItem {Text = "Tom", Value = "12"},
        new SelectListItem {Text = "Jerry", Value = "13"}
    };
    vm.EmployeeIds= new int[] { 12,13} ;  
    return View(vm);
}

This will select the option Tom and Jerry in the multi select element when the page is rendered.

Using ViewBag to transfer the list of items

If you do not prefer to keep a collection type property to pass the list of options to the view, you can use the dynamic ViewBag to do so.(This is not my personally recommended approach as viewbag is dynamic and your code is prone to uncatched typo errors)

public IActionResult Create()
{       
    ViewBag.Employees = new List<SelectListItem>
    {
        new SelectListItem {Text = "Shyju", Value = "1"},
        new SelectListItem {Text = "Sean", Value = "2"}
    };
    return View(new MyViewModel());
}

and in the view

<select asp-for="EmployeeId" asp-items="@ViewBag.Employees">
    <option>Please select one</option>
</select>

Using ViewBag to transfer the list of items and setting selected option

It is same as above. All you have to do is, set the property (for which you are binding the dropdown for) value to the value of the option you want to be selected.

public IActionResult Create()
{       
    ViewBag.Employees = new List<SelectListItem>
    {
        new SelectListItem {Text = "Shyju", Value = "1"},
        new SelectListItem {Text = "Bryan", Value = "2"},
        new SelectListItem {Text = "Sean", Value = "3"}
    };

    vm.EmployeeId = 2;  // This will set Bryan as selected

    return View(new MyViewModel());
}

and in the view

<select asp-for="EmployeeId" asp-items="@ViewBag.Employees">
    <option>Please select one</option>
</select>

Grouping items

The select tag helper method supports grouping options in a dropdown. All you have to do is, specify the Group property value of each SelectListItem in your action method.

public IActionResult Create()
{
    var vm = new MyViewModel();

    var group1 = new SelectListGroup { Name = "Dev Team" };
    var group2 = new SelectListGroup { Name = "QA Team" };

    var employeeList = new List<SelectListItem>()
    {
        new SelectListItem() { Value = "1", Text = "Shyju", Group = group1 },
        new SelectListItem() { Value = "2", Text = "Bryan", Group = group1 },
        new SelectListItem() { Value = "3", Text = "Kevin", Group = group2 },
        new SelectListItem() { Value = "4", Text = "Alex", Group = group2 }
    };
    vm.Employees = employeeList;
    return View(vm);
}

There is no change in the view code. the select tag helper will now render the options inside 2 optgroup items.

Tuesday, June 1, 2021
 
Andres
answered 7 Months ago
71

You can just insert an option item inside the select:

<select asp-for="Category" asp-items="@ViewBag.Category"  class="form-control">
    <option disabled selected>--- SELECT ---</option>
</select>

The disabled keyword in the code above means that the "--- SELECT ---" row cannot be picked again once a choice has been selected in the list. If you want the user to be able to blank the selection again (i.e. if it's bound to a nullable field) then just omit disabled.

Saturday, September 11, 2021
 
Viralk
answered 3 Months ago
65

You can achieve this by building and registering a custom display metadata provider. There are libraries that will perform more elaborate "humanization" to the property names, but you can achieve something pretty useful with some trusty regular expressions.

public class HumanizerMetadataProvider : IDisplayMetadataProvider
{
    public void CreateDisplayMetadata(DisplayMetadataProviderContext context)
    {
        var propertyAttributes = context.Attributes;
        var modelMetadata = context.DisplayMetadata;
        var propertyName = context.Key.Name;

        if (IsTransformRequired(propertyName, modelMetadata, propertyAttributes))
        {
            modelMetadata.DisplayName = () => SplitCamelCase(propertyName);
        }
    }

    private static string SplitCamelCase(string str)
    {
        return Regex.Replace(
            Regex.Replace(
                str,
                @"(P{Ll})(P{Ll}p{Ll})",
                "$1 $2"
            ),
            @"(p{Ll})(P{Ll})",
            "$1 $2"
        );
    }

    private static bool IsTransformRequired(string propertyName, DisplayMetadata modelMetadata, IReadOnlyList<object> propertyAttributes)
    {
        if (!string.IsNullOrEmpty(modelMetadata.SimpleDisplayProperty))
            return false;

        if (propertyAttributes.OfType<DisplayNameAttribute>().Any())
            return false;

        if (propertyAttributes.OfType<DisplayAttribute>().Any())
            return false;

        if (string.IsNullOrEmpty(propertyName))
            return false;

        return true;
    }
}

The IsTransformRequired method ensures that you can still override the provider with a decorated property when you need to.

Register the provider on startup through the AddMvcOptions method on IMvcBuilder:

builder.AddMvcOptions(m => m.ModelMetadataDetailsProviders.Add(new HumanizerMetadataProvider()));
Saturday, October 2, 2021
 
Sabya
answered 2 Months ago
42

If you don't want the asp-for attribute to pull from the Model directly you can override that behavior by providing an @.

Aka:

<select asp-for="@ViewBag.XYZ">
    ...
</select>

Therefore, based on what you said I believe your bit becomes:

@model IEnumerable<InvoiceIT.Models.Invoice>
<form asp-controller="Invoice" asp-action="Index" method="post" class="form-horizontal" role="form">
@{
    SelectList companies = ViewBag.Companies;
    var currentlySelectedIndex = 0; // Currently selected index (usually will come from model)
}
    <select asp-for="@currentlySelectedIndex" asp-items="companies" class="form-control">
    </select>
    <input type="submit" value="Save" class="btn btn-default" />
</form>

Hopefully this helps!

Tuesday, October 12, 2021
 
James
answered 2 Months ago
43

Finally I found the solution!

It doesn't seem obvious but that way I have no compilation erros. The answer I got from Ivan was not incorrect, but it was necessary to import the CRM.Model on the view like:

@using CRM.Model;

So, my dropdown:

<select asp-for="TipoLog" asp-items="Html.GetEnumSelectList<ETipoLog>()" class="form-control"></select>

enter image description here

You can see, Visual Studio told me it was unessessary, painting it in grey, but without that, I get compilation error. I hope I can help someone else.

Sunday, October 17, 2021
 
Carlo Pellegrini
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share