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.
And our breakpoint shows we’re getting our status value from the form.
Gotchas
Here are a few mistakes some folks may run into and may want to keep in mind when working with these tag helpers.
- Do NOT add any additional HTML tags inside your select HTML element. ASP.NET will not function because you are overriding its natural behavior.
-
SelectListItem
has more properties, and we can utilize them to make for more user-friendly UIs. - Remember to use
BindProperty
, or your Razor Page will not function. -
name
attributes are important. Without them, your data will not make it to the server. - The
enum
type might not be structurally sufficient to provide us with the metadata we want, like display names, groups, etc. - 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.