Razor syntax and helpers

Razor syntax and helpers

Razor is a HTML templating engine which allows us to construct HTML pages from a combination of data and HTML markup. The power of Razor resides in its typesafety and support for well known operators, conditional if else and iterators for while foreach. It allows us to directly use our C# models in the templates and call functions accessible by the view. Together with intellisense, it is quick way to build UI.
Today we will explore some of the features from Razor:

 1. Syntax
 2. Layout and partial views
 3. View components
 4. Tag helpers

1. Syntax

Razor expressions can written in two ways, implicit or explicit notation.

For implicit notation, we start with @ and then we directly have access to values either from the view itself or some we created in the view or static functions.

@{ 
    var a = 10;
}

<label>@a</label>

One useful property of the view is the Model.
It gives access to the model linked to the page. By going @Model.XXX, we can access the property of the model for display or manipulation purposes.
To set the type of the model we can use the special keyword @model.

For every changes made on the view, only a refresh of the browser is required. The default behaviour is that Razor views are compiled at runtime when the view is invoked unless configured otherwise.

The explicit notation is used with parentheses. It is usefull for simple manipulation:

@{
    var a = 10;
    var b = 15;
}

<p>Total: @(a + b)</p>

In the previous two sample we also saw that code blocks can be placed directly into the views using @{ ... }. Code blocks can also be created by control structures like @if, @for and others. A list can be found in the official razor doc https://docs.microsoft.com/en-us/aspnet/core/mvc/views/razor.

In code blocks, we saw that C# code can be written, but we can also add HTML code in the code block.

@{
    var n = 1;
    for(var i = 0; i < n; i++)
    {
        <p>@i</p>
    }
}

This is known as an implicit code block transition where html tags are written directly within the code block.

Since code written in code block expect C#, if we want to add normal html text, the IDE will interpret it as C#.
In order to interpret it as html text we can use <text>, also known as explicit code block transition:

@{
    var personName = "Kim";
    <text>Name: @personName</text>
}

Lastly if we have a single line we can use the explicit line code block transition with @:. The previous example can be simplified to:

@{
    var personName = "Kim";
    @:Name: @personName
}

2. Layout and partial views

Layout

Each view has a Layout property. It defines a common layout where view can be put in.
The main layout is created by the default template _Layout.cshtml in the Shared folder.

It is set on all views:

@{ Layout = _Layout }

This is specified in the ViewStart.cshtml which is run before each view.

In the layout, we call the functions RenderBody() and RenderSection(...) which are functions to be used by the layout to specify where the view will be placed.

For RenderSection it is used to render a section defined by the view.

If in the layout we have the following RenderSection:

<body>
    <div>
        @RenderBody()
    </div>
    @RenderSection("Scripts", false)
</body>

In the view we would expect the following expression defining the Scripts section:

@section Scripts {
    <script type="text/javascript" src="/app.js"></script>
}

The Scripts section will be inserted where the @RenderSection is placed on the layout.

ViewStart and ViewImports are two special files automatically discovered by the runtime.
ViewStart is used to place code which runs before each view, here we talked about setting the layout as example, another typical example would be to set the title of the pages.
ViewImports is used to place all the namespace import, by default we have the current assembly namespace plus the tag helper namespace.

@using RazorTest.Web2
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

For the import of tag helpers, * means importing all from the Microsoft.AspNetCore.Mvc.TagHelpers assembly.

Partial views

To use partial views, we can use the Html property of the view which we access using @Html, and for partial we invoke the Partial function:

@Html.Partial("view", model)

Here we specified the name as view which specify to Razor to search the view in current folder or shared folder.
If we would have specified view.cshtml it would have only searched in the current folder.
A model can also be passed to a partial view.

For example we could have a list of Company.

public class Company
{
    public string Name { get; set; }
    public string Address { get; set; }
    public string Description { get; set; }
}

And have a main page listing the company:

@model IEnumerable<Company>

@foreach (var comp in @Model) { 
    @Html.Partial("CompanyCard", comp)
}

But somehow we judged that the piece of display would be a reusable component named CompanyCard which could be used in other places.
And we define in the /Shared folder a CompanyCard.cshtml which displays the company info:

@model Company

<div class="card">
    <h1>@Model.Name</h1>
    <p>@Model.Address</p>
</div>

3. View components

One of the latest add-on on Razor are the View components. Before we could only have partial views to segment our views. But partial views were limited to pieces of view itself with a model bound to it. So we could only put reusable pieces of html markup together. As we saw in (2), we could have a piece of markup which shows a company card and have that as a reusable partial view.
But there are times where a whole piece of the view plus the logic backing the view can be segmented and use as a reusable component. This is what the view components are.

A view component defines logic in C# through a class inheriting from ViewComponent and has a view tight to it.

The view of the component view must be placed in a specific folder for the runtime to discover it. The two locations are:

  • Views/<controller_name>/Components/<view_component_name>/<view_name> this is preferable if we want the view component to be available for our controller only.
  • Views/Shared/Components/<view_component_name>/<view_name> this is preferable if we want the view component to be available as a shared component for the whole system.

The default name of a view of the view component is Default. Therefore if within the view component we call View() without specifying a view view component name, the Default view will be used.

View component are NOT controllers. They do NOT substitute controller endpoints. They are simply a separation of a piece of reusable view with the logic to construct the view.

The main function of the view component is the InvokeAsync. The function isn’t a requirement from an interface or abstract class, it is called by name convention InvokeAsync. Calling by convention makes the function extremely flexible as the parameters are not fixed. The function then accept an arbitrary number of parameters.

For example we will be creating a CompanyList View component:

public class CompanyListViewComponent : ViewComponent
{
    public Task<IViewComponentResult> InvokeAsync(int count)
    {
        var items = new List<Company> {
                new Company {
                    Name = "ABC"
                },
                new Company {
                    Name = "HELLO"
                },
                new Company {
                    Name = "WORLD"
                },
                new Company {
                    Name = "BYE"
                }
        }
        .Take(count)
        .ToList();

        return Task.FromResult((IViewComponentResult)View(items));
    }
}

Then from a Razor view we can invoke the component by calling the static function Component.InvokeAsync(...):

@await Component.InvokeAsync("CompanyList", new { count = 2 })

If we do need to serve the view component itself instead of a view, we can use the ViewComponent(...) function in the MVC controller:

public IActionResult Index()
{
    return ViewComponent("CompanyList", new { count = 1 });
}

4. Tag helpers

Another latest add-on on Razor are the tag helpers. Tag helpers are HTML tags which take over certain HTML tags and provide attributes which can be filled up in a typesafe way together with intellisense help. When the tag needs to be rendered the tag helper will generate the necessary html tag, the necessary attributes and also add other html tags if required.

For example we used to have to use @Html.LabelFor to create a label for a model property input.
Now we can use the label tag helper which contains a asp-for attribute.

For example the following example, we are using the form, div, label and input tag helpers:

@model Company

<form method="post" asp-controller="Home">
    <div asp-validation-summary="All" class="text-danger"></div>
    <div class="form-group">
        <label asp-for="Name">Name</label>
        <input asp-for="Name" type="text" />
    </div>
    <button type="submit">Submit</button>
</form>

With the following viewmodel and data annotation:

public class Company
{
    [Required]
    public string Name { get; set; }
    public string Address { get; set; }
    public string Description { get; set; }
}

Generates the following HTML markup:

<form method="post" action="/Home/companies">
    <div class="text-danger validation-summary-valid" data-valmsg-summary="true">
        <ul>
            <li style="display:none"></li>
        </ul>
    </div>
    <div class="form-group">
        <label for="Name">Name</label>
        <input type="text" data-val="true" data-val-required="The Name field is required." id="Name" name="Name" value="">
    </div>
    <button type="submit">Submit</button>
    <input name="__RequestVerificationToken" type="hidden" value="CfDJ8DpoVLSsI-dFmCuXII29cPzMmmt_tFkEOLe9OnmvXItzk_j8lygsGE1Yw2rVDDwLqhLV4HXo7_BgizkVYoyCFB6QaoG4v1xMLnwlZd3_dqeBANt1pOuOa5rq9TiSAEhYFH9iIL75wiVoQwE5KiA9QK8">
</form>

We can see that the input has been created with special attributes containing a validation error according to our data annotation. Also we can see that an antiforgery token has been added as __RequestVerificationToken. This is automatic when using form tag helper.

As we saw, some of the common tag helpers are the form tag helper which has the asp-controller and asp-action add-ons, the label tag helper with asp-for and the input tag helper with asp-for and asp-validation-for.

The select tag helper is also very useful as we can display a selection list for an enumeration:

public enum Domain
{
    Finance,
    Marketing
}
<div class="form-group">
    <label asp-for="Domain">Domain</label>
    <select asp-for="Domain" asp-items="Html.GetEnumSelectList<Domain>()"></select>
</div>

Html is a property on the view which gives access to basic functionalities like GetEnumSelectList which transforms an enum into a selection list.
Display attribute can then be used to display a human readable string.

For form fields, a comprehensive list is available in ASP NET core documentation https://docs.microsoft.com/en-us/aspnet/core/mvc/views/working-with-forms.

Note: There is a known problem currently where intellisense for tag helper isn’t working.

Tag Helpers do not work
Issue: Razor Tag Helpers do not get colorization or special IntelliSense at design time.  They work normally at runtime.
Workaround: Install the Razor Language Service extension.

Source: https://github.com/aspnet/Tooling/blob/master/known-issues-vs2017.md#tag-helpers-do-not-work

If it is the case for you, install the VS extension for Razor https://marketplace.visualstudio.com/items?itemName=ms-madsk.RazorLanguageServices.

Conclusion

Today we discovered the syntax and features offered by Razor. Using Razor to build static pages is straight forward and the new add-ons of tag helpers and view components are here to allow us to build larger application with better isolation of reusable view parts by embracing a component based architecture model. Hope you enjoyed this post, if you have any question leave it here or hit me on Twitter @Kimserey_Lam.See you next time!

Comments

Popular posts from this blog

A complete SignalR with ASP Net Core example with WSS, Authentication, Nginx

Verify dotnet SDK and runtime version installed

SDK-Style project and project.assets.json