ASP.NET’s Razor engine comes with an assortment of HTML tag helpers. Some of the helpers are straightforward, while others may be challenging for folks to grasp. One of the more powerful HTML elements is select, allowing us to select a value from a set of options.

In this post, we’ll see how we can structure our ASP.NET models and views to get the most out of the select HTML element.

Getting Started

In most cases, we’ll have a model that has a property that can contain a single value for a property. The values for this property may come from an enum or a list of string values. In this case, let’s look at an Order class which has an OrderStatus property named Status.

public class Order
{
    public int Id { get; set; }
    public DateTime OrderedAt { get; set; }
    public string Username { get; set; }
    public OrderStatus Status { get; set; } = OrderStatus.New;
}

public enum OrderStatus
{
    New,
    Pending,
    Shipped,
    Delivered
}

The Order class is a typical model we may have in our application’s domain model. Let’s move on to setting up our ViewModel.

ViewModel and PageModel

Let’s clarify what a ViewModel is for those of us not familiar with the concept. A ViewModel is a surrogate between our backend services and the view in our application. These C# classes provide commands, values, and valuable context for our views. When using MVC, we need to create our own ViewModels, but when using Razor Pages, we can utilize the PageModel. In this example, we’ll be using Razor Pages.

Our PageModel will have a single Order instance that we’ll hardcode into our class for the sake of this example.

public class IndexModel : PageModel
{
    static Order State { get; } = new Order
    {
        Id = 1,
        Username = "Khalid.Abuhakmeh",
        OrderedAt = DateTime.Now,
        Status = OrderStatus.New
    };
    
    public Order Current { get; set; }
    
    public void OnGet()
    {
        Current = State;
    }
}

When the page loads on a GET request, we will pass the state of our order to our Order property. Let’s move on to adding the options of statuses to our page model.

SelectListItem

The HTML select tag helpers for Razor use an abstraction model called SelectListItem to create a collection of suitable values. In our page model, we’ll be creating a collection of order statuses.

For reusability, I’ve created this static helper to work with the Enum types found in our example.

public static class SelectList
{
    public static List<SelectListItem> Create<TEnum>
        (bool includeEmptyOption = false)
    {
        var type = typeof(TEnum);
        if (!type.IsEnum)
        {
            throw new ArgumentException(
                "type must be an enum",
                nameof(type)
            );
        }

        var result =
            Enum
                .GetValues(type)
                .Cast<TEnum>()
                .Select(v => 
                    new SelectListItem(
                    v.ToString(),
                    v.ToString()
                    )
                )
                .ToList();

        if (includeEmptyOption)
        {
            // Insert the empty option
            // at the beginning
            result.Insert(
                0, 
                new SelectListItem("Pick An Option", "")
            );
        }

        return result;
    }
}

We can now add the collection of SelectListItem to our page model. We will also need to add the BindProperty attribute to our Current property to allow us to post and bind values to it.

public class IndexModel : PageModel
{
    static Order State { get; } = new Order
    {
        Id = 1,
        Username = "Khalid.Abuhakmeh",
        OrderedAt = DateTime.Now,
        Status = OrderStatus.New
    };
    
    [BindProperty] public Order Current { get; set; }
    public List<SelectListItem> Statuses { get; } 
        = SelectList.Create<OrderStatus>();
    
    public void OnGet()
    {
        Current = State;
    }
}

In the next section, we’ll see how we utilize our page model to bind to the Razor HTML tag helpers.

HTML & Razor

In this section, we’ll be using some tag helpers to create a select dropdown with our known OrderStatus values.

<form action="" method="post">
    <label asp-for="Current.Id">
        Order #@Model.Current.Id (@Model.Current.OrderedAt.ToShortDateString())
        <input type="hidden" asp-for="Current.Id" />
        <input type="hidden" asp-for="Current.OrderedAt" />
    </label>
    <label asp-for="Current.Username">
        <input type="hidden" asp-for="Current.Username"/>
    </label>
    
    <label asp-for="Current.Status">
    </label>
    <select asp-for="Current.Status" asp-items="Model.Statuses">
    </select>
    <button type="submit">Save Order</button>
</form>

We’ll also need to add an OnPost method to our Razor Page. Our updated page model will look like this.

public class IndexModel : PageModel
{
    private static Order State { get; } = new Order
    {
        Id = 1,
        Username = "Khalid.Abuhakmeh",
        OrderedAt = DateTime.Now,
        Status = OrderStatus.New
    };

    [BindProperty] public Order Current { get; set; }
    public List<SelectListItem> Statuses { get; } 
        = SelectList.Create<OrderStatus>();

    public void OnGet()
    {
        Current = State;
    }

    public void OnPost()
    {
        State.Status = Current.Status;
        Current = State;
    }
}

Now we have a functioning select HTML element that updates the state of our Current order on the backend. Cool! The form works but could use some design flourishes. Here it is in action on the front end.

working with the form

And our breakpoint shows we’re getting our status value from the form.

posted select value to server

Gotchas

Here are a few mistakes some folks may run into and may want to keep in mind when working with these tag helpers.

  1. Do NOT add any additional HTML tags inside your select HTML element. ASP.NET will not function because you are overriding its natural behavior.
  2. SelectListItem has more properties, and we can utilize them to make for more user-friendly UIs.
  3. Remember to use BindProperty, or your Razor Page will not function.
  4. name attributes are important. Without them, your data will not make it to the server.
  5. The enum type might not be structurally sufficient to provide us with the metadata we want, like display names, groups, etc.
  6. Our form needs a submit input type, or nothing will happen when we click our button.

Conclusion

HTML forms are not magic; they work in predictable ways. Razor syntax builds on top of the predictable approaches designed into the HTML specification. By understanding how HTML works, we can shape our backend code to provide the most straightforward and cleanest approach needed to build our Razor views. I hope this post was helpful and please leave a comment or questions below.