Sample: Blazor components

Overview

Use the Blazor sample to create two widgets that use Blazor components:

  • Calculator widget – displays a calculator that can perform basic calculations.
  • Authorized widget – displays the currently logged user
PREREQUISITES: Before you implement the sample, you must:
  • Set up a Sitefinity renderer application and connect it to your Sitefinity CMS application.
    For more information, see Install Sitefinity in ASP.NET Core mode.
  • In your Renderer application, you must install the NuGet package
    Microsoft.AspNetCore.Components.nupkg

NOTE: The instructions in this sample use Visual Studio 2022 and a Sitefinity renderer project named Renderer.

Blazor components

The sample works by wrapping the Blazor component (CalculatorComponent.razor) into a view component (CalculatorViewComponent.cs), which is displayed in the WYSIWYG editor as a widget. By using the out-of-the-box implementation for view components and their integration into the WYSIWYG editor, you can configure the Blazor components via the autogenerated designers.

You can then pass the configured values into the Blazor component as parameters through the view of the view component (Default.cshtml).
You achieve this by calling the RenderComponent method:
@(await Html.RenderComponentAsync<CalculatorComponent>
  (RenderMode.Server, new {Title = @Model.Title }))

NOTE: The Blazor widgets work with the rendering modes Server and ServerPrerendered.

Render content in edit or preview mode

There is the option to conditionally display content during the editing of the page. You can achieve this with the help of the NavigationManager class. The Calculator component (CalculatorComponent.razor) inherits from a base class called BlazorBase.cs that holds two methods IsEdit and IsPreview. They are used to determine the current state of the page - whether it is opened in edit or not.

Create the folder structure

Under your Renderer project, you must create the following folders:

  • Components
  • Entities/Calculator
  • ViewComponents
  • Views/Shared/Components/Authorized
  • Views/Shared/Components/Calculator

Create a custom layout file

You create a special layout file for pages that will hold the Blazor components. The key points is:
<script src="_framework/blazor.server.js"></script>
This includes the Blazor framework in the HTML. It is important to have it right before the closing <body> tag

Perform the following:

  1. In the context menu of folder Views/Shared, click Add » Class…
  2. Select Code File.
  3. In Name, enter BlazorLayout.cshtml and click Add.
  4. In the file, paste the following code and save your changes:

    @model Progress.Sitefinity.AspNetCore.Models.PageModel
    @using Progress.Sitefinity.AspNetCore.Mvc.Rendering
    @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
    @addTagHelper *, Progress.Sitefinity.AspNetCore
    @inject Progress.Sitefinity.AspNetCore.Web.IRequestContext requestContext;
    @inject Progress.Sitefinity.AspNetCore.Web.IRenderContext renderContext;
    <!DOCTYPE html>
    <html id="html" lang="@requestContext.Culture">
    <head>
    <base href="~/" />
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    @Html.RenderSeoMeta(this.Model)
    <environment include="Development">
    <link rel="stylesheet" href="@Progress.Sitefinity.AspNetCore.Constants.PrefixRendererUrl("Styles/bootstrap.css")" />
    </environment>
    <environment exclude="Development">
    <link rel="stylesheet" href="@Progress.Sitefinity.AspNetCore.Constants.PrefixRendererUrl("Styles/bootstrap.min.css")" />
    </environment>
    @* Scripts for the OOB Sitefinity widgets *@
    <link rel="stylesheet" href="@Progress.Sitefinity.AspNetCore.Constants.PrefixRendererUrl("styles/main.css")" type="text/css" />
    @* Custom styles *@
    <link rel="stylesheet" href="~/styles/styles.css" />
    @* Custom scripts *@
    <script src="~/scripts/scripts.js"></script>
    </head>
    <body class="container-fluid">
    @RenderSection("Scripts")
    @* Start - Code valid for versions < 14.2.7921 *@
    @* For Older versions you can include the file directly in edit and live,
    but it causes a 'race condition' during editing of the page and inconsistently breaks the rendering in the dom *@
    @* <script src="_framework/blazor.server.js"></script> *@
    @* End - Code valid for versions < 14.2.7921 *@
    @* Start - Code valid for versions >= 14.2.7921 *@
    @if (renderContext.IsEdit)
    {
    @* Important to be at the end of the body tag *@
    <script src="_framework/blazor.server.js" autostart="false"></script>
    <script>
    document.addEventListener("DOMContentLoaded", function() {
    const savedVal = window["rendererContract"];
    window["rendererContract"] = undefined;
    function triggerTimeout(timeout) {
    return setTimeout(() => {
    window["rendererContract"] = savedVal;
    window.dispatchEvent(new Event('contractReady'));
    }, timeout);
    }
    Blazor.start().then(() => {
    let handle = triggerTimeout(1000);
    window.componentRendered = function() {
    if (handle) {
    clearTimeout(handle);
    }
    handle = triggerTimeout(100);
    };
    });
    });
    </script>
    }
    else
    {
    <script src="_framework/blazor.server.js"></script>
    }
    @* End - Code valid for versions >= 14.2.7921 *@
    </body>
    </html>

Implement the authentication

To have the current user available in the Blazor components, you create a custom authentication provider class.
Perform the following:

  1. In the context menu of your Renderer project, click Add » Class…
  2. In Name, enter CustomAuthenticationStateProvider.cs and click Add.
  3. In the class, paste the following code and save your changes:

    using System.Collections.Generic;
    using System.Linq;
    using System.Security.Claims;
    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components.Authorization;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Net.Http.Headers;
    using Progress.Sitefinity.RestSdk;
    using Progress.Sitefinity.RestSdk.Clients.Users.Dto;
    public class CustomAuthenticationStateProvider : AuthenticationStateProvider
    {
    private readonly IHttpContextAccessor httpContextAccessor;
    public CustomAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor)
    {
    this.httpContextAccessor = httpContextAccessor;
    }
    public override async Task<AuthenticationState> GetAuthenticationStateAsync()
    {
    if (this.httpContextAccessor.HttpContext.Request.Path.HasValue && this.httpContextAccessor.HttpContext.Request.Path.Value.StartsWith("/_blazor", System.StringComparison.Ordinal))
    {
    var args = new Progress.Sitefinity.RestSdk.RequestArgs();
    var requestCookie = this.httpContextAccessor.HttpContext.Request.Headers[HeaderNames.Cookie];
    if (!string.IsNullOrEmpty(requestCookie))
    args.AdditionalHeaders.Add(HeaderNames.Cookie, requestCookie);
    var restClient = this.httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IRestClient>();
    await restClient.Init(args);
    var userData = await restClient.Users().GetCurrentUser();
    if (userData.IsAuthenticated)
    {
    var principal = GetPrincipal(userData);
    return new AuthenticationState(principal);
    }
    }
    return new AuthenticationState(this.httpContextAccessor.HttpContext.User);
    }
    private static ClaimsPrincipal GetPrincipal(UserDto user)
    {
    var claims = new List<Claim>();
    if (!string.IsNullOrEmpty(user.Email))
    claims.Add(new Claim(ClaimTypes.Email, user.Email));
    if (!string.IsNullOrEmpty(user.Username))
    claims.Add(new Claim(ClaimTypes.Name, user.Username));
    if (user.Roles != null)
    {
    foreach (var role in user.Roles)
    {
    var roleClaim = new Claim(ClaimTypes.Role, role);
    claims.Add(roleClaim);
    }
    }
    if (user.Claims != null && user.Claims.Count > 0)
    {
    var converted = user.Claims
    .Where(x => x.Type != ClaimTypes.Name && x.Type != ClaimTypes.Email)
    .Select(x => new Claim(x.Type, x.Value, x.ValueType, x.Issuer, x.OriginalIssuer))
    .ToList();
    claims.AddRange(converted);
    }
    var identity = new ClaimsIdentity(claims, user.AuthenticationProtocol ?? "Default");
    return new ClaimsPrincipal(identity);
    }
    }

Create the Blazor components

Perform the following

  1. In the context menu of folder Components, click Add » Class…
  2. Select Code File.
  3. In Name, enter _Imports.razor and click Add.
  4. In the file, paste the following code and save your changes:

    @using System.Net.Http
    @using Microsoft.AspNetCore.Authorization
    @using Microsoft.AspNetCore.Components.Authorization
    @using Microsoft.AspNetCore.Components.Forms
    @using Microsoft.AspNetCore.Components.Routing
    @using Microsoft.AspNetCore.Components.Web
    @using Microsoft.JSInterop
    @using System.IO
    view raw _Imports.razor hosted with ❤ by GitHub

Perform the following:

  1. In the context menu of folder Components, click Add » Class…
  2. In Name, enter BlazorBase.cs and click Add.
  3. In the class, paste the following code and save your changes:

    using System.Threading.Tasks;
    using Microsoft.AspNetCore.Components;
    using Microsoft.JSInterop;
    namespace Renderer.Components
    {
    public class BlazorBase: ComponentBase
    {
    [Inject]
    private NavigationManager navigationManager { get; set; }
    [Inject]
    private IJSRuntime JS { get; set; }
    public bool IsEdit
    {
    get
    {
    return this.navigationManager.Uri.Contains("sfaction=edit");
    }
    }
    public bool IsPreview
    {
    get
    {
    return this.navigationManager.Uri.Contains("sfaction=preview");
    }
    }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
    if (this.IsEdit)
    {
    await JS.InvokeVoidAsync("componentRendered");
    }
    await base.OnAfterRenderAsync(firstRender);
    }
    }
    }
    view raw BlazorBase.cs hosted with ❤ by GitHub

Perform the following:

  1. In the context menu of folder Components, click Add » Class…
  2. Select Code File.
  3. In Name, enter CalculatorComponent.razor and click Add.
  4. In the file, paste the following code and save your changes:

    @inherits BlazorBase
    @if (this.IsEdit)
    {
    <h1>@Title</h1>
    <hr />
    }
    <div>
    <div class="row">
    <div class="col-md-3">
    <p>First Number</p>
    </div>
    <div class="col-md-4">
    <input placeholder="Enter First Number" @bind="@num1" />
    </div>
    </div>
    <br />
    <div class="row">
    <div class="col-md-3">
    <p>Second Number</p>
    </div>
    <div class="col-md-4">
    <input placeholder="Enter Second Number" @bind="@num2" />
    </div>
    </div>
    <br />
    <div class="row">
    <div class="col-md-3">
    <p>Result</p>
    </div>
    <div class="col-md-4">
    <input readonly @bind="@result" />
    </div>
    </div>
    <br />
    <div class="row">
    <div class="col-md-2">
    <button @onclick="AddNumbers" class="btn btn-light">Add (+)</button>
    </div>
    <div class="col-md-2">
    <button @onclick="SubtractNumbers" class="btn btn-primary">Subtract (−)</button>
    </div>
    <div class="col-md-2">
    <button @onclick="MultiplyNumbers" class="btn btn-success ">Multiply (X)</button>
    </div>
    <div class="col-md-2">
    <button @onclick="DivideNumbers" class="btn btn-info">Divide (/)</button>
    </div>
    </div>
    </div>
    @code {
    [Parameter]
    public string Title { get; set; }
    string num1;
    string num2;
    string result;
    void AddNumbers()
    {
    result = (Convert.ToDouble(num1) + Convert.ToDouble(num2)).ToString();
    }
    void SubtractNumbers()
    {
    result = (Convert.ToDouble(num1) - Convert.ToDouble(num2)).ToString();
    }
    void MultiplyNumbers()
    {
    result = (Convert.ToDouble(num1) * Convert.ToDouble(num2)).ToString();
    }
    void DivideNumbers()
    {
    if (Convert.ToDouble(num2) != 0)
    {
    result = (Convert.ToDouble(num1) / Convert.ToDouble(num2)).ToString();
    }
    else
    {
    result = "Cannot Divide by Zero";
    }
    }
    }

Perform the following:

  1. In the context menu of folder Components, click Add » Class…
  2. Select Code File.
  3. In Name, enter AuthorizedComponent.razor and click Add.
  4. In the file, paste the following code and save your changes:

    <CascadingAuthenticationState>
    <AuthorizeView>
    <Authorized>
    <p>Hello, @context.User.Identity?.Name!</p>
    </Authorized>
    <NotAuthorized>
    <p>You're not authorized.</p>
    </NotAuthorized>
    </AuthorizeView>
    </CascadingAuthenticationState>

Create the widgets

Perform the following:

  1. In the context menu of folder Entities/Calculator, click Add » Class…
  2. In Name, enter CalculatorEntity.cs and click Add.
  3. In the class, paste the following code and save your changes:

    using System.ComponentModel;
    namespace Renderer.Entities.Calculator
    {
    /// <summary>
    /// The test model for the load tests widget.
    /// </summary>
    public class CalculatorEntity
    {
    /// <summary>
    /// Gets or sets a value indicating whether a Boolean property is true or false.
    /// </summary>
    [DefaultValue("Basic Calculator Demo Using Blazor")]
    public string Title { get; set; }
    }
    }

Perform the following:

  1. In the context menu of folder ViewComponents, click Add » Class…
  2. In Name, enter CalculatorViewComponent.cs and click Add.
  3. In the class, paste the following code and save your changes:

    using System;
    using Renderer.Entities.Calculator;
    using Microsoft.AspNetCore.Mvc;
    using Progress.Sitefinity.AspNetCore.ViewComponents;
    namespace Renderer.ViewComponents
    {
    /// <summary>
    /// Test widget with different kinds of restrictions for its properties.
    /// </summary>
    [SitefinityWidget]
    public class CalculatorViewComponent : ViewComponent
    {
    /// <summary>
    /// Invokes the view.
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public IViewComponentResult Invoke(IViewComponentContext<CalculatorEntity> context)
    {
    if (context == null)
    {
    throw new ArgumentNullException(nameof(context));
    }
    return this.View(context.Entity);
    }
    }
    }

Perform the following:

  1. In the context menu of folder ViewComponents, click Add » Class…
  2. In Name, enter AuthorizedViewComponent.cs and click Add.
  3. In the class, paste the following code and save your changes:

    using System;
    using Renderer.Entities.Calculator;
    using Microsoft.AspNetCore.Mvc;
    using Progress.Sitefinity.AspNetCore.ViewComponents;
    namespace Renderer.ViewComponents
    {
    /// <summary>
    /// Test widget with different kind of restrictions for its properties.
    /// </summary>
    [SitefinityWidget]
    public class AuthorizedViewComponent : ViewComponent
    {
    /// <summary>
    /// Invokes the view.
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    public IViewComponentResult Invoke(IViewComponentContext context)
    {
    if (context == null)
    {
    throw new ArgumentNullException(nameof(context));
    }
    return this.View();
    }
    }
    }

Perform the following:

  1. In the context menu of folder Views/Shared/Components/Calculator, click Add » Class…
  2. Select Code File.
  3. In Name, enter Default.cshtml and click Add.
  4. In the file, paste the following code and save your changes:

    @model Renderer.Entities.Calculator.CalculatorEntity
    @using Renderer.Components;
    @(await Html.RenderComponentAsync<CalculatorComponent>(RenderMode.ServerPrerendered, new {Title = @Model.Title }))
    view raw Default.cshtml hosted with ❤ by GitHub

Perform the following:

  1. In the context menu of folder Views/Shared/Components/Authorized, click Add » Class…
  2. Select Code File.
  3. In Name, enter Default.cshtml and click Add.
    In the file, paste the following code and save your changes:

    @using Renderer.Components;
    @(await Html.RenderComponentAsync<AuthorizedComponent>(RenderMode.ServerPrerendered))
    view raw Default.cshtml hosted with ❤ by GitHub

Integrate the Blazor framework and register the authentication provider

You integrate ASP.NET Core Blazor framework in your project by modifying the Program.cs file to include the necessary Blazor services and endpoints. You also need to register the authentication provider.

The Program.cs file should look in the following way:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Progress.Sitefinity.AspNetCore;
using Progress.Sitefinity.AspNetCore.FormWidgets;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
/// Add services to the container
builder.Services.AddSitefinity();
builder.Services.AddViewComponentModels();
builder.Services.AddFormViewComponentModels();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseStaticFiles();
app.UseRouting();
app.UseSitefinity();
app.MapBlazorHub();
app.Run();
/// adds the needed services
builder.Services.AddServerSideBlazor();
/// static files are necessary in order to be able to deliver the blazor framework scripts
app.UseStaticFiles();
/// configures the blazor endpoints
app..MapBlazorHub();
view raw Program.cs hosted with ❤ by GitHub

Build your solution.

Result

When you open your Renderer application and open the New editor, you will see the Authorized and the Calculator widgets in the widget selector. When you add the widgets on your page, you can see the currently logged user and a calculator widget, where you can perform simple operations.

Blazor

Want to learn more?

Increase your Sitefinity skills by signing up for our free trainings. Get Sitefinity-certified at Progress Education Community to boost your credentials.

Get started with Integration Hub | Sitefinity Cloud | Sitefinity SaaS

This free lesson teaches administrators, marketers, and other business professionals how to use the Integration hub service to create automated workflows between Sitefinity and other business systems.

Web Security for Sitefinity Administrators

This free lesson teaches administrators the basics about protecting yor Sitefinity instance and its sites from external threats. Configure HTTPS, SSL, allow lists for trusted sites, and cookie security, among others.

Foundations of Sitefinity ASP.NET Core Development

The free on-demand video course teaches developers how to use Sitefinity .NET Core and leverage its decoupled architecture and new way of coding against the platform.

Was this article helpful?