Tag Archives: Razor Pages

Tag Helper Authoring in ASP .NET Core

By Shahed C on May 21, 2019

This is the twentieth of a series of posts on ASP .NET Core in 2019. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2019, titled A-Z of ASP .NET Core!

ASPNETCoreLogo-300x267 A – Z of ASP .NET Core!

In this Article:

T is for Tag Helper Authoring

Tag Helpers are very useful for ASP .NET Core developers in creating HTML elements with server-side attributes. They work equally well in both Razor Pages and MVC views. Better yet, the syntax allows a front-end developer to easily customize the UI, with HTML/CSS knowledge.

If you need a refresher on built-in tag helpers in ASP .NET Core, you may revisit an earlier post in this series:

TagHelpers-Razor-MVC

Authoring your own tag helpers is as easy as implementing the ITagHelper interface. To make things easier, the TagHelper class (which already implements the aforementioned interface) can be extended to build your custom tag helpers.

To follow along, take a look at the sample project on Github:

Web Tag Helper Authoring Sample: https://github.com/shahedc/TagHelperAuthoring30

CAUTION: the sample code contains spoilers for Avengers: Endgame (2019).

Custom Tag Helpers

As with most concepts introduced in ASP .NET Core, it helps to use named folders and conventions to ease the development process. In the case of Tag Helpers, you should start with a “TagHelpers” folder at the root-level of your project for your convenience. You can save your custom tag helper classes in this folder.

This blog post and its corresponding code sample builds upon the official tutorial for authoring tag helpers. While the official tutorial covers instructions for MVC views, this blog post takes a look at a Razor Page example. The creation of Tag Helpers involves the same process in either case. Let’s start with the synchronous and asynchronous versions of a Tag Helper that formats email addresses.

The class EmailTagHelper.cs defines a tag helper that is a subclass of the TagHelper class, saved in the “TagHelpers” folder. It contains a Process() method that changes the output of the HTML tag it is generating.

public class EmailTagHelper : TagHelper
{
   ...
   // synchronous method, CANNOT call output.GetChildContentAsync();
   public override void Process(TagHelperContext context, TagHelperOutput output)
   {
      // ...
   } 
}

The class AsyncEmailTagHelper.cs defines a tag helper that is also a subclass of the TagHelper class, saved in the aforementioned “TagHelpers” folder. It contains a ProcessAsync() method, which has a different signature (returns Task object instead of void) and grabs the child content from the output using output.GetChildContentAsync();

public class AsyncEmailTagHelper : TagHelper
{
   ...
   // ASYNC method, REQUIRED to call output.GetChildContentAsync();
   public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
   {
      // ... 
   }
}

In order to use the tag helper in a Razor Page, simply add a using statement for the Tag Helper’s namespace, and then include a custom HTML tag that has the same name as the Tag Helper’s class name (without the TagHelper suffix). For the Email and AsyncEmail Tag Helpers, the corresponding tags in your Razor Page would be <email> and <async-email> respectively.

In the EmailTester.cshtml page:

<email mail-to="Black.Widow"></email>

In the AsyncEmailTester.cshtml page:

<async-email>Black.Widow</async-email>

Note that the PascalCase capitalization in the class name corresponds to a lowercase tag in kebab-case. In a browser, the end result includes a clickable email link from the Razor Pages. Both the non-async and async version of the methods produce similar end results.

Email Tester in a Razor Page

Email Tester in a Razor Page

Async Email Tester in a Razor Page

Async Email Tester in a Razor Page

Setting Attributes and Content

So how does the Process() method convert your custom tags into valid HTML tags? It does that in a series of steps.

  1. Set the HTML element as the tag name to replace it with, e.g. <a>
  2. Set each attribute within that HTML element, e.g. href
  3. Set HTML Content within the tags.

The process involved is slightly different between the synchronous and asynchronous versions of the Process method. In the synchronous EmailTagHelper.cs class, the Process() method does the following:

// 1. Set the HTML element
output.TagName = "a"; 

// 2. Set the href attribute
output.Attributes.SetAttribute("href", "mailto:" + address);

// 3. Set HTML Content
output.Content.SetContent(address);

In the asynchronous AsyncEmailTagHelper.cs class, the ProcessAsync() method does the following:

// 1. Set the HTML element
output.TagName = "a"; 

var content = await output.GetChildContentAsync();
var target = content.GetContent() + "@" + EmailDomain;

// 2. Set the href attribute within that HTML element, e.g. href
output.Attributes.SetAttribute("href", "mailto:" + target);

// 3. Set HTML Content
output.Content.SetContent(target);

The difference between the two is that the async method gets the output content asynchronously with some additional steps. Before setting the attribute in Step 2, it grabs the output content from GetChildContentAsync() and then uses content.GetContent() to extract the content before setting the attribute with output.Attributes.SetAttribute(). 

Updating Pre/Post Content

This section recaps the BoldTagHelper explained in the docs tutorial, by consolidating all the lessons learned. In the BoldTagHelper.cs class from the sample, you can see the following code:

[HtmlTargetElement("bold")]
[HtmlTargetElement(Attributes = "bold")]
public class BoldTagHelper : TagHelper
{
   public override void Process(TagHelperContext context, TagHelperOutput output)
   {
      output.Attributes.RemoveAll("bold");
      output.PreContent.SetHtmlContent("<strong>");
      output.PostContent.SetHtmlContent("</strong>");
   }
}

Let’s go over what the code does, line by line:

  • The [HtmlTargetElement] attribute forces a Tag Helper to target a specific element, e.g. [HtmlTargetElement(“bold”)], which will target a <bold> tag in a Razor Page or MVC View.
  • When one or more attributes are specified, e.g. [HtmlTargetElement(Attributes = “bold”)], the Tag Helper targets a bold attribute within an element, e.g. <p bold>
  • Combining the above one after the other gives you an OR condition, in which either scenario can be matched.
  • Combining them in a single [HtmlTargetElement] creates an AND condition, which would match a bold tag with a bold attribute, which is not very useful, e.g. [HtmlTargetElement(“bold”, Attributes = “bold”)]

Here is a snippet the corresponding Razor Page for testing the above scenario, BoldTester.cshtml:

<p bold>This paragraph uses a P tag with a bold attribute. 
Using a tag helper, the entire paragraph should be displayed with bold text.</p>

<bold>This statement uses a custom bold tag to be displayed in bold.</bold>

The tag helper affects both fragments, as seen in the screenshot below:

TagHelpers-BoldTester

The statements in the Process() method accomplish the following:

  • The RemoveAll() method from output.Attributes removes the “bold” attribute within the tag, as it is essentially acting as a placeholder.
  • The SetHtmlContent() from output.PreContent adds an opening <strong> tag  inside the enclosing element, i.e. just after <p> or <bold>
  • The SetHtmlContent() from output.Postontent adds a closing </strong> tag  inside the enclosing element, i.e. just before </p> or </bold>

Passing Complex Objects

What if you want to pass a more complex object, with properties and objects within it? This can be done by defining a C# model class, e.g. SuperheroModel.cs, that can be initialized inside in the Page Model class (SuperheroInfoTesterModel.cs) and then used in a Razor Page (SuperheroInfoTester.cshtml). The tag helper (SuperheroTagHelper.cs) then brings it all together by replacing <superhero> tags with whatever SuperHeroModel info is passed in.

Let’s take a look at all its parts, and how it all comes together.

Object ModelSuperheroModel.cs

public class SuperheroModel
{
   public string LastName { get; set; }
   public string FirstName { get; set; }
   public string SuperName { get; set; }
   public bool HasSurvived { get; set; }

   public bool ShowInfoWithSpoilers { get; set; }
}

Razor PageSuperheroInfoTester.cshtml

@page
@model SuperheroInfoTesterModel

... 

<h3>Black Widow Info:</h3>
<div condition="@Model.blackWidowInfo.ShowInfoWithSpoilers">
 <superhero hero-info="Model.blackWidowInfo" />
</div>
...

Page Model for Razor Page: SuperheroInfoTester.cshtml.cs

public class SuperheroInfoTesterModel : PageModel
{
   public SuperheroModel blackWidowInfo { get; set; }
   // ...

   public void OnGet()
   {
      blackWidowInfo = new SuperheroModel
      {
         // ...
      }
      // ...
   }
}

Superhero Tag HelperSuperheroTagHelper.cs

public class SuperheroTagHelper : TagHelper
{
   public SuperheroModel HeroInfo { get; set; }

   public override void Process(TagHelperContext context, TagHelperOutput output)
   {
     // ...
   }
}

Going through the above code:

  1. The tag helper is named SuperheroTagHelper, implying that it can be used for <superhero> tags in a Razor Page, e.g. SuperHeroInfoTester.cshtml
  2. The tag helper also contains a SuperheroModel object called HeroInfo, which allows a hero-info attribute, i.e. <superhero hero-info=”Model.property”>
  3. The SuperheroModel class contains various public properties that provide information about a specific superhero.
  4. The SuperHeroInfoTesterModel page model class includes an OnGet() method that initializes multiple SuperheroModel objects, to be displayed in the Razor Page.

Inside the tag helper, the Process() method takes care of replacing the <superhero> tag with a <section> tag:

public override void Process(TagHelperContext context, TagHelperOutput output)
{
   string htmlContent = $@"<ul><li><strong>First Name:</strong> {HeroInfo.FirstName}</li>
<li><strong>Last Name:</strong> {HeroInfo.LastName}</li>
<li><strong>Superhero Name:</strong> {HeroInfo.SuperName}</li>
<li><strong>Survived Endgame? </strong> {HeroInfo.HasSurvived}</li></ul>";
 
   output.TagName = "section";
   output.Content.SetHtmlContent(htmlContent);
   output.TagMode = TagMode.StartTagAndEndTag; 
}

After initializing some HTML content to display a <ul> list, the above code in the Process() method accomplishes the following:

  1. Set the HTML element as the tag name to replace it with, e.g. <section>
  2. Set HTML Content within the tags.
  3. Set Tag Mode to include both start and end tags, e.g. <section> … </section>

End Result in Browser:

TagHelpers-Model

In a web browser, you can see that that the <superhero> tag has been converted into a <section> tag with <ul> content.

Handling Conditions

When you want to handle a UI element in different ways based on certain conditions, you may use a ConditionTagHelper. In this case, a condition is used to determine whether spoilers for the popular movie Avengers: Endgame should be displayed or not. If the spoiler flag is set to false, the character’s info is not displayed at all.

@page
@model SuperheroInfoTesterModel
...
<div condition="@Model.blackWidowInfo.ShowInfoWithSpoilers">
 <superhero hero-info="Model.blackWidowInfo" />
</div>
...

In the above code from the SuperheroInfoTester.cshtml page:

  • the <div> includes a condition that evaluates a boolean value, e.g. Model.blackWidowInfo.ShowInfoWithSpoilers
  • the Model object comes from the @model defined at the top of the page
  • the boolean value of ShowInfoWithSpoilers determines whether the <div> is displayed or not.

References

 

Razor Pages in ASP .NET Core

By Shahed C on May 8, 2019

This is the eighteenth of a series of posts on ASP .NET Core in 2019. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2019, titled A-Z of ASP .NET Core!

ASPNETCoreLogo-300x267 A – Z of ASP .NET Core!

In this Article:

R is for Razor Pages

Razor Pages were introduced in ASP .NET Core v2.0, and briefly covered in my 2018 series. The post covered Pages in ASP .NET Core: Razor, Blazor and MVC Views. This post in the 2019 A-Z series will go deeper into Razor Pages and some of its features. You may also refer to a previous post to learn more about Forms and Fields (specifically the Razor Pages section).

Built on top of MVC in ASP .NET Core, Razor Pages allows you to simplify the way you organize and code your web apps. Your Razor Pages may coexist along with a backend Web API and/or traditional MVC views backed by controllers. Razor Pages are typically backed by a corresponding .cs class file, which represents a Model for the Page with Model Properties and Action Methods that represent HTTP Verbs. You can even use your Razor knowledge to work on Blazor fullstack web development.

Razor-Pages-Diagram

Core 3.0 Packages

To follow along, take a look at the sample project on Github:

Web Razor Pages (Core 3.0) Sample: https://github.com/shahedc/RazorPagesCore30

Let’s start by taking a look at a 3.0 project (currently in preview) compared to a 2.x project (seen in 2018’s post on Pages). The snippet below shows a .csproj for the sample app. This was created by starting with the Core 3.0 (Preview) Razor Pages Template in VS2019 and then following the official tutorial on Docs.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.0</TargetFramework>
  </PropertyGroup>


  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-preview3-19153-02" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0-preview3.19153.1" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0-preview3.19153.1">
    ...
  </ItemGroup>

</Project>

For ASP .NET Core 3.0, both NewtonsoftJson and EF Core have been removed from the ASP .NET Core shared framework. Instead, they are available as NuGet packages that can be included via <PackageReference> tags in the .csproj project file.

This is reflected in the Solution Explorer, where the Dependencies tree shows the NewtonsoftJson and EF Core packages nested under the NuGet node.

Razor-Pages-SolutionExplorer

If you need a refresher on the new changes for ASP .NET Core 3.0, refer to the following:

Page Syntax

To develop Razor Pages, you can reuse syntax from MVC Razor Views, including Tag Helpers, etc. For more information on Tag Helpers, stay tuned for an upcoming post in this series. The code snippet below shows a typical Razor page, e.g. Index.cshtml:

@page
@model IndexModel
@{
 ViewData["Title"] = "Home page";
}

<!-- HTML content, with Tag Helpers, model attributes -->

Here is a quick recap of what a Razor Page is made of:

  1. Each Razor Page starts with an @page directive to indicate that it’s a Razor Page. This is different from Razor Views in MVC, which should not start with @page.
  2. The @page directive may be followed by an @model directive. This identifies the corresponding C# model class, typically located in the same folder as the .cshtml page itself.
  3. (Optional) You can include server-side code within an @{} block.
  4. The rest of the page should include any HTML content you would like to display. This includes any server-side Tag Helpers and Model attributes.

Running the sample app shows the Movies Index page in action:

e.g. https://localhost:44301/Movies

Razor-Pages-Movies-Index

Model Binding

The .cs model class associated  with the page includes both the model’s attributes, as well as action methods for HTTP Verbs. In a way, it consolidates the functionality of an MVC Controller and C# viewmodel class, within a single class file.

The simplest way to use model binding in a Razor Page use to use the [BindProperty] attribute on properties defined in the model class. This may include both simple and complex objects. In the sample, the Movie property in the CreateModel class is decorated with this attribute as shown below:

[BindProperty]
public Movie Movie { get; set; }

Note that [BindProperty] allows you to bind properties for HTTP POST requests by default. However, you will have to explicitly opt-in for HTTP GET requests. This can be accomplished by including an optional boolean parameter (SupportsGet) and setting it to True.

[BindProperty(SupportsGet = true)]
public string SearchString { get; set; }

This may come in handy when passing in QueryString parameters to be consumed by your Razor Page. Parameters are optional and are part of the route used to access your Razor Pages.

To use the Model’s properties, you can use the syntax Model.Property to refer to each property by name. Instead of using the name of the model, you have to use the actual word “Model” in your Razor Page code.

e.g. a page’s model could have a complex object…

public Movie Movie { get; set; }

Within the complex object, e.g. the Movie class has a public ID property:

public int ID { get; set; }

In the Razor Page that refers to the above model, you can refer to Model.Movie.ID by name:

@page
@model RazorPagesCore30.Pages.Movies.DetailsModel
...
<a asp-page="./Edit" asp-route-id="@Model.Movie.ID">Edit</a>

In this particular example, the <a> anchor tag is generated with a link to the Edit page with a route that uses a specific Movie ID value. The link points to the Edit page in the current subfolder (i.e. “Movies”), indicated by the period and slash in the path. The generated HTML looks like the following:

<a href="/Movies/Edit?id=6">Edit</a>

Razor-Pages-Movies-Details

Page Parameters

Page parameters can be included with the @page directive at the top of the page. To indicate that a parameter is optional, you may include a trailing ? question mark character after the parameter name. You may also couple the parameter names with a data type, e.g. int for integers.

@page "{id}"

@page "{id?}"

@page "{id:int?}"

The above snippet shows 3 different options for an id parameter, an optional id parameter and an integer-enforced id parameter. In the C# model code, a property named id can be automatically bound to the page parameter by using the aforementioned [BindProperty] attribute.

In the sample, the  SearchString property in the IndexModel class for Movies shows this in action.

[BindProperty(SupportsGet = true)]
public string SearchString { get; set; }

The corresponding page can then define an optional searchString parameter with the @page directive. In the HTML content that follows, Input Tag Helpers can be used to bind an HTML field (e.g. an input text field) to the field.

@page "{searchString?}"
...
Title: <input type="text" asp-for="SearchString" />

Page Routing

As you may have guessed, a page parameter is setting up route data, allowing you to access the page using a route that includes the page name and parameter:

e.g. https://servername/PageName/?ParameterName=ParameterValue

In the sample project, browsing to the Movies page with the search string “Iron” shows a series of “Iron Man” movies, as shown in the following screenshot.

e.g. https://localhost:44301/Movies/?SearchString=Iron

Razor-Pages-Movies-Search

Here, the value for SearchString is used by the OnGetAsync() method in the Index.cshtml.cs class for the Movies page. In the code snippet below, you can see that a LINQ Query filters the movies by a subset of movies where the Title contains the SearchString value. Finally, the list of movies is assigned to the Movie list object.

...
public IList<Movie> Movie { get;set; }
...
public async Task OnGetAsync()
{
   ...
   if (!string.IsNullOrEmpty(SearchString))
   {
      movies = movies.Where(s => s.Title.Contains(SearchString));
   }
   ...
   Movie = await movies.ToListAsync();
}

By convention, all Razor Pages should be in a root-level “Pages” folder. Think of this “Pages” folder as a virtual root of your web application. To create a link to a Razor Page, you may link to the name of a Razor Page at the root level (e.g. “/Index”) or a Razor Page within a subfolder (e.g. “/Movies/Index”).

<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>

<a class="nav-link text-dark" asp-area="" asp-page="/Movies/Index">MCU Movies</a>

Razor-Pages-Movies-Routes

Handler Methods

The OnGetAsync() method seen in the previous method is triggered when the Razor Page is triggered by an HTTP GET request that matches its route data. In addition to OnGetAsync(), you can find a complete list of Handler Methods that correspond to all HTTP verbs. The most common ones are for GET and POST:

  • OnGet() or OnGetAsync for HTTP GET
  • OnPost() or OnPostAsync for HTTP POST

When using the Async alternatives for each handler methods, you should return a Task object (or void for the non-async version). To include a return value, you should return a Task<IActionResult> (or IActionResult for the non-async version).

public void OnGet() {}
public IActionResult OnGet() {}
public async Task OnGetAsync() {}

public void OnPost() {}
public IActionResult OnPost() {}
public async Task<IActionResult> OnPostAsync() {}

To implement custom handler methods, you can handle more than one action in the same HTML form. To accomplish this, use the asp-page-handler attribute on an HTML <button> to handle different scenarios.

<form method="post">
 <button asp-page-handler="Handler1">Button 1</button>
 <button asp-page-handler="Handler2">Button 2</button>
</form>

To respond to this custom handlers, the exact handler names (e.g. Handler1 and Handler2) need to be included after OnPost in the handler methods. The snippet below shows the corresponding examples for handling the two buttons.

public async Task<IActionResult> OnPostHandler1Async()
{
   //...
}
public async Task<IActionResult> OnPostHandler2Info()
{
   // ...
}

NOTE: if you need to create a public method that you don’t have to be recognized as a handler method, you should decorate such a method with the [NonHandler] attribute.

References

 

Authentication & Authorization in ASP .NET Core

By Shahed C on January 7, 2019

This is the first of a new series of posts on ASP .NET Core for 2019. In this series, we’ll cover 26 topics over a span of 26 weeks from January through June 2019, titled A-Z of ASP .NET Core!

ASPNETCoreLogo-300x267 A – Z of ASP .NET Core!

In this Article:

A is for Authentication & Authorization

Authentication and Authorization are two different things, but they also go hand in hand. Think of Authentication as letting someone into your home and Authorization as allowing your guests to do specific things once they’re inside (e.g. wear their shoes indoors, eat your food, etc). In other words, Authentication lets your web app’s users identify themselves to get access to your app and Authorization allows them to get access to specific features and functionality.

In this article, we will take a look at the NetLearner app, on how specific pages can be restricted to users who are logged in to the application. Throughout the series, I will try to focus on new code added to NetLearner or build a smaller sample app if necessary.

Authentication in ASP .NET Core

The quickest way to add authentication to your ASP .NET Core app is to use of the pre-built templates with one of the Authentication options. The examples below demonstrate both the CLI commands and Visual Studio UI.

CLI Commands:

> dotnet new webapp --auth Individual

Visual Studio 2017 new project with Authentication:

Change Authentication upon creating a new project

Change Authentication upon creating a new project

Select Authentication Type

Select Authentication Type

The above example uses “Individual” authentication, which offers a couple of options:

  • Store user accounts in-app: includes a local user accounts store
  • Connect to an existing user store in the cloud: connect to an existing Azure AD B2C application

Even if I choose to start with a local database, I can update the connection string to point to a SQL Server instance on my network or in the cloud, depending on which configuration is being loaded. If you’re wondering where your Identity code lives, check out my previous post on Razor UI Libraries, and jump to the last section where Identity is mentioned.

From the documentation, the types of authentication are listed below.

  • None: No authentication
  • Individual: Individual authentication
  • IndividualB2C: Individual authentication with Azure AD B2C
  • SingleOrg: Organizational authentication for a single tenant
  • MultiOrg: Organizational authentication for multiple tenants
  • Windows: Windows authentication

To get help information about Authentication types, simply type ––help after the ––auth flag, e.g.

> dotnet new webapp --auth --help

Authentication in NetLearner

Within my NetLearner app, the following snippets of code are added to the Startup.cs configuration:

public void ConfigureServices(IServiceCollection services)
{
...
   services.AddDbContext<ApplicationDbContext>(options =>
      options.UseSqlServer(
      Configuration.GetConnectionString("DefaultConnection")));

   services.AddDefaultIdentity<IdentityUser>()
      .AddDefaultUI(UIFramework.Bootstrap4)
      .AddEntityFrameworkStores<ApplicationDbContext>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
 app.UseStaticFiles();
...
 app.UseAuthentication();
...
 app.UseMvc();
}

In the above, note that:

  • The ConfigureServices() method has calls to services.AddDbContext and server.AddDefaultIdentity. The call to add a DB Context will vary depending on which data store you choose for authentication. The call to AddDefaultIdentity ensures that your app calls AddIdentity, AddDefaultUI and AddDefaultTokenProviders to add common identity features and user Register/Login functionality.
  • The Configure() method has a call to app.UseAuthentication to ensure that authentication is used by your web app. Note that this appears after app.UseStaticFiles but before app.UseMvc to ensure that static files (html, css, js, etc) can be served without any authentication but MVC application-controlled routes and views/pages will follow authentication rules.

Continue reading