ASP.NET Core uses a new coding writing design mode named Tag Helpers
replacing the original Html Helpers
utlity in order to simplify server-side HTML generation. This library is an supplement for the official Microsoft.AspNetCore.Mvc.TagHelpers
package and added a series of new tag helpers. This page will demostrate the usages all tag helpers provided in the Sakura.AspNetCore.Mvc.TagHelpers
library.
To use new tag helpers provided in this library, you must first install and configure the package. In order to install it to your project, please open project.json
file and add a new depenedency named Sakura.AspNetCore.Mvc.TagHelpers
in the dependencies
section.
And then, you should enable new tag helpers in your MVC view. The most simple way to enable all tag helpers for all pages is add a new line in _ViewImports.cs
files as follow:
@addTagHelper *, Sakura.AspNetCore.Mvc.TagHelpers
If you need to enable/disable a custom tag helper in a specified page, please see the documentation for @addTagHelper
directive.
This section will describle the usage of all tag helpers.
Generation a list of all enum items and allow user to select one of them is a common task in MVC projects. Currently, you may use HtmlHelper.GenerateEnumList
to generated a SelectItemList
object and then set it to asp-items
attributes on an <select>
tags. These steps can work, however it is a bit complex as well as you can not control the generated item's name and value, thus it will be UI unfriendly since the enum names are always short terms without spaces between words, and it also cannot be localizable.
Use the EnumSelectForTagHelper
You can now use asp-enum-for
attribute to generate a HTML select list with options for an enum type, the type is specified by the given model expression. The following code shows the basic way for this tag helper:
// backend file
public enum ProjectAccessType
{
[Display(Description = "Everyone can access this project")]
Public,
[Display(Description = "Only specified users can access this project")]
Private,
[Display(Description = "Everyone can access and update this project")]
Community
}
public class Project
{
public ProjectAccessType AccessType { get; set; }
}
<!-- In MVC view page -->
@model Project
<select asp-enum-for="AccessType"></select>
The actual page will be generated as:
<select name="AccessType">
<option value="Public">Public</option>
<option value="Private">Private</option>
<option value="Community">Community</option>
</select>
An important enhancement provied by this tag helper is that you can use asp-text-source
and asp-value-source
to control the generated text and value of options. The tag helper will detect any DisplayAttribute
applied on each enum item, and get the property value specified by asp-text-source
attribute as the display text of options. e.g. The following code:
<!-- In MVC view page -->
@model Project
<select asp-enum-for="AccessType" asp-text-source="Description"></select>
will generate the following HTML (using the same backend type definition as first sample):
<select name="AccessType">
<option value="Public">Everyone can access this project</option>
<option value="Private">Only specified users can access this project</option>
<option value="Community">Everyone can access and update this project</option>
</select>
If none of asp-text-source
is specified, the default value is set to EnumNameOnly
, which will generate the result as the same as the first sample. If some enum items is lack of specified inforation (e.g. Description
is null), the tag helper will also fallback to using the enum name as the text.
The asp-value-source
is used to control the value of options. The default value of this attribute is set to Name
, which means the name of the enum item is used as the option value. If needed, you can change this attribute to Value
, in such case, the following code:
<!-- In MVC view page -->
@model Project
<select asp-enum-for="AccessType" asp-value-source="Value"></select>
Will be generated as:
<select name="AccessType">
<option value="0">Public</option>
<option value="1">Private</option>
<option value="2">Community</option>
</select>
Note: The default MVC model binders can handle enum names correctly, thus you usually do not need to set this attribute.
The EnumSelectTypeTagHelper
is similar ot EnumSelectForTagHelper
, however you use asp-enum-type
to specify the enum type explicitly instead of inferring it from a model expression. In such case you may also need to add the name
attribute for the <select>
tag manually in order to send its data to server correctly. The following code is a sample usage and will generate the same HTML as the above one:
<select asp-enum-type="@typeof(ProjectAccessType)" name="AccessType"></select>
Another common task related to <select>
tag is to initially set the default selected value according to the current state of data when a user tries to edit a existing data item. In ASP.NET Core MVC projects, you can use asp-for
for a <select>
element to automatically set the initial state according to the model value. However, if you are trying to display a select list without directly model binding, you must using one of the following manner:
- build-up a
SelectList
instance and provideselectedValue
argument during construction, and then applyasp-items
attribute on the<select>
element. - Define each
<option>
element in HTML and using a Rozar condition expression to theselected
attribute.
This first manner is more simple for value calculation, however you will write a lot of C# code for generating a static list. The second manner is more graceful in HTML generation, however you must repeat the selected
condition on each option.
Now with the new tag helper, you can simplely apply an asp-value
attribute on any <select>
tag, and tag helper will help you to automatically make the correct option selected during the page generation. e.g. The following code:
<select asp-value="@myValue">
<option value="1">1</option>
<option value="2">2</option>
</select>
will generate the following HTML is the value of myValue
is equal to 1
:
<select>
<option value="1" selected="selected">1</option>
<option value="2">2</option>
</select>
Note: The value of asp-value
attribute is considered as a string since HTML only accept string as element content. You may need to convert value of other types into string manually.
Additionally, you can specify the asp-value-compare-mode
on the <select>
tag to control how to determine the specified value is equal to the option value. The default setting is OrdinalIgnoreCase
, which can be used in mose cases, but you can change it to any enum item in System.StringComparison
type to change this behavior.
This is a shortcut tag helper used to add a option label (option with empty value) into a <select>
tag. You may use asp-option-label
to set the label text and asp-option-label-position
to set the location of the label. A sample can be:
<select asp-option-label="Select a option" asp-option-label-position="First">
<option value="1">1</option>
<option value="1">1</option>
</select>
The above code will generate the folloing HTML:
<select>
<option value="">Select a option</option>
<option value="1">1</option>
<option value="1">1</option>
</select>
Flags enum is a common design pattern used to store multiple flags into a composed value, and filter values using arbitray flag combination. For end users, each flag can be considered as a individual option, and is usually represented as a checkbox. However, the default MVC model binding system does not allow to bind a single flag with a checkbox's check state.
With the new tag helper, you can now use asp-flag-enum-for
and asp-flag-enum-value
attributes on checkbox to correctly bind a certern flag of a model value with the input element. e.g. the following code:
// backend code
[Flags]
public enum Features
{
None = 0x0,
CanRead = 0x1,
CanWrite = 0x2,
CanSeek = 0x4
}
public class Stream
{
public Features Features { get; set; }
}
<!-- In MVC view page -->
@model Stream
<label>
<input type="checkbox" asp-enum-flag-for="Features" asp-enum-flag-value="Features.CanSeek"> CanSeek
</label>
will generate the following code if the model's Features
property contains the flag CanSeek
:
<label>
<input type="checkbox" name="Features" value="CanSeek" checked="checked"> CanSeek
</label>
NOTE: This tag helper only helps you to generate an input element. Unforetunately, the default MVC model binding system cannot handle flags enum items and merge them correctly. In order to receive the final data in you back end callback action, you may also need the FlagsEnumModelBinder
feature, this binder can be found in the next section.
In classical ASP.NET Project, Html.DisplayText
method is used to display labels with data annotation related dynamic data model. This method is also inherited by the new ASP.NET Core framework. Now for tag helper enabled new Razor page syntax, this method is a bit obesoleted. Another problem for this method is that it lakes the control of text source.
The new <display-text>
tag is designed to provide modern and flexible data annotation based text extraction, together with standard ASP.NET Core localization framework. Similar with the EnumSelectForTagHelper
, it also provides the text-source
attribute to control the display text source. For example, a backend data model may be defined as:
public class Account
{
[Display(Name="User Name")]
public string UserName { get; set; }
}
And when a page is designed to show or edit account information, you may want to extract the Name
property defined in the DisplayAttribute
as some label text, in this case you may write:
@model Account
<label><display-text for="UserName" text-source="Name" /></label>
Then the <display-text>
tag will be converted to a plain HTML content text "User Name" in place.
Convert a enum item to a user friendly display text is a common task in lots of appliations. In .NET Framework, enum types are auto sealed and thus we cannot add new methods or override existing methods (especially for the ToString
method). This limitation brings much more work when we trying to convert enum item to a custom string.
A typical solution is defining an extension method such as GetString
and then call this method in the source code. This manner works, however it is lake of the variation ability in different sneces (i.e. when you want to show long and short display strings in two pages) and localization supports. Using DisplayAtttribute
combined with resource files is a generic way to add full-level visualization support for them, while it needs reflection operation and thus it is not easy to use it.
Now the <enum-item-display-text>
tag helper wrapped all necessary works for you and you may just use the DisplayAttribute
to define display texts for enum items with localization support. For example, an enum type and a typical usage are defined as:
public enum UserType
{
[Display(Name = "Generic User")]
User,
[Display(Name = "System Adminisrator")]
Admin,
}
public class User
{
public UserType UserType { get; set; }
}
And for a page is designed for a User
model, you may write:
@model User
<p>Your user type: <enum-item-display-text for="UserType" text-source="Name" /><p>
Then the placeholder tag helper will be replaced with text "Generic User" if your user type value is UserType.User
and "System Administrator" if your type is UserType.Admin
.
Besides the for
attribute, you may also use the value
attribute if you want to show the display text for an existing enum value directly which is not directly related with the page model (and is difficult to extract with for
attribute). The text-source
attribute can be used as the same as some other tag helpers above.
CSS class is an important part for HTML element. It can be used to both styling and item filtering. An element can belong to one or more classes sepecifed by class
attribute. The class
attribute is a composed value contains a list of class names, each of them a sepereated with spaces. You cannot specify multiple class
attributes on a single element, and if you wish to apply some class conditionally, you may:
- Generate the entire class list using C# and set the value to
class
attribute, or - Generate a string of class name or empty string according to the condition, and insert it as a part or class string
Neither of those methods are friendly to code reading. Now using the new tag helper, you can simplely append any class on a single element with different conditions using asp-conditional-class-<className>
attribute. Here is a simple usage demo:
<a class="page" asp-conditional-class-active="currentPage == 1" href="?page=1">Page 1<a>
If the C# expresion condition currentPage == 1
is satisfied, the above code will generate:
<a class="page active" href="?page=1">Page 1<a>
While if the condition is not satisfied, this attribute will be simplely ignored.
Providing different content according to user's role is a common task in morden web applications. Usually the user authorization checking should be done in the backend (controlleres and actions), writing complex service invocations in CSHTML file may be not a good idea. However, sometimes it is still accepable to do so, especially when backend coding is not available (e.g. for layout files). To make user authorization check in CSHTML files, you need to inject a service with type IAuthorizationService
and invoke the AuthorizeAsync
method together with await
and if
keywords. This manner is a bit lengthy, and also mixing Razor blocks and HTML is not friendly for code maintance.
Now with these two tag helpers, you can easy add HTML style code to show/hide contents according to the current user's permission. The easiest usage is as simple as add one new asp-authorize-policy
tag-attribute in any HTMl element, which is provided by AuthorizeAttributeTagHelper
, a simple code is just like following:
<div asp-authorize-policy="Edit">
<!-- any content -->
</div>
The above code make a authorization check on the during the page generation, and if the current user meets the requirements for the Edit
policy defined in this application, this element and its content will be rendered as usual; otherwise, this element and all its content will be removed. The removal is done in backend, thus the end user cannot found any hint in the final HTML.
Note: This TagHelper only supports policy-based authorization (which are similar as the major usage of the AuthorizeAttribute
), Roles-based or other type authorization is not supported. To learn how to define policies in your application, please see the ASP.NET Core Official Documentation.
The AuthorizeAttributeTagHelper
also supports an additional attribute named asp-authorize-resource
to support the resource
argument in IAuthorizationService.AuthorizeAsync
method. This argument is not used in the default authorization service provided by ASP.NET Core library, and thus you usually do not need to set it. However, if you are using any 3rd authorization middleware which may take benefit from this extra argument, you can provide the value easily within HTML style code.
The AuthorizeAttributeTagHelper
can be used in any existing HTML element, however, only one root element and its content can be affected in one time. If you want to affect multiple neighboring same level elements, a standalone <authorize>
tag is provided in AuthorizeTagHelper
. Its usage is similar as the above, the following code shows an example:
<!-- you may also use "resource" attribute to provide resource argument if necessary -->
<authorize policy="Edit">
<div></div>
<div></div>
</authorize>
You may see the attribute name is simplified and it can also surrounds a series of elements.
In a complex page, parts of layout may be repeated, e.g. A page of product introduction may has several user comments. Usually you control all of them using class filters or DOM tree traveling, however, sometimes you may need to control each of them invidually. Although you can capture them by location, using ID to pick-up one element is usually more efficient and straightforward. The HTML standard requires every ID must be unique in each page, thus for a server-side repeated range (usually generated by for
or foreach
), id must be dynamically generated inside the loop range.
The IdFormatTagHelper
provide asp-id-format
attribute on element in order to generate a id according to a specified format. The final id will be generated using string.Format
method, will the placeholder {0}
will a unique number start from one. Here is a example:
@foreach (var i in Items)
{
<div asp-id-format="region-{0}"></div>
}
The above code will generated the following HTML if the Items
has 3 elements:
<div asp-id-format="region-1"></div>
<div asp-id-format="region-2"></div>
<div asp-id-format="region-3"></div>
Note: Currently the tag helper use the format string as the counting key, that means all element uses a same value of asp-id-format
will share the counting number. The count is registered and saved in the view data dictionary, which means the entire page processing pipeline (including the layout page, main page, any partial page and view components) will be considered as a single document and the counting will be continous in the final page.
The FlagsEnumInputTagHelper
can help you to generate flag checkboxs, however, default MVC model binder cannot automatically merge all the values. To solve this problem, you may add the FlagsEnumModelBinder
into you MVC middleware, you may add it in the service configurating phase using the following code:
// In startup.cs
public void ConfigurationServices(IServiceCollection services)
{
// Other configuration code
services.AddMvc(options =>
{
// Using the followint code to add the new binder
options.AddFlagsEnumModelBinderProvider();
});
}