Time travel into the future – BLAZOR + SITECORE + HELIX


People I’m back with the wonderful BLAZOR. It has been a tough month but it was worth it. Finally, we now have an offline Sitecore-Helix-BLAZOR version ๐Ÿ™‚

I also want to say that this wouldn’t be possible without JSS – Sitecore Javascript Services. The people behind JSS has/is done/doing one hell off a job, keep up the good work!

For you guys who still are wondering what BLAZOR is, check out this great post – WHAT IS BLAZOR?

It’s BLAZOOOOR time, let’s set it up and may the Power Glove guide you

Setup/install BLAZOR

  1. Install the .NET Core 2.1 SDK (2.1.300 or later).
  2. Install Visual Studio 2017 Preview with the ASP.NET and web development workload selected.
  3. Install the latest Blazor Language Services extension from the Visual Studio
    Marketplace.

BLAZOR is installed, next will be to grab the code from Github ๐Ÿ™‚
Or as Hackerman says – Let’s get going, we are gone hack this 3.5 inch floppy disc to the year 2005

Sitecore + Blazor solution

Locate solution, Sitecore + BLAZOR at github: https://github.com/GoranHalvarsson/SitecoreBlazor
Clone it, build it and be happy ๐Ÿ™‚

Let’s have a look at the solution, we are of course following the HELIX concept here.

The SitecoreBlazorHosted.Client project will act as “HELIX Project”. SitecoreBlazorHosted.Shared is something new, the cool thing with BLAZOR is that we use C# client-side. That means we can share classes between server-side and client-side, hence the name SitecoreBlazorHosted.Shared.

Ok, so how does it work then?
A typical BLAZOR application normally has one or many pages, using a master layout(You can also have nested layouts). But in our case we want to make it all dynamic, that also includes the pages. Instead, we will use the master layout as a page. Similar to the Default.cshtml(Layout) in an Asp.Net MVC solution or to a MasterPage in an Asp.Net Webforms solution.

Behold MasterBlaster.cshtml in the SitecoreBlazorHosted.Client project:

@using Foundation.BlazorExtensions.Services

@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper uriHelper;

@inject LayoutService layoutService;

@inherits BlazorLayoutComponent
@implements IDisposable


<div class="main">

    <BlazorPlaceholder Name="navbar">
    </BlazorPlaceholder>

    <main role="main" class="container">
        <BlazorPlaceholder Name="main">
        </BlazorPlaceholder>
    </main>


    <footer class="container">

        <BlazorPlaceholder Name="footer">
        </BlazorPlaceholder>

    </footer>
</div>


@functions
{

    [Parameter]
    protected string Language { get; set; }

    [Parameter]
    protected string PageUrl { get; set; }

    protected override void OnInit()
    {
        uriHelper.OnLocationChanged += OnLocationChanges;
        Reload();
    }

    private void OnLocationChanges(object sender, string location) => Reload();

    private async void Reload()
    {
        await layoutService.LoadRoute(Language);
        StateHasChanged();
    }

    public void Dispose()
    {
        uriHelper.OnLocationChanged -= OnLocationChanges;
    }

}

At github: https://github.com/GoranHalvarsson/SitecoreBlazor/blob/master/SitecoreBlazorHosted.Client/Shared/MasterBlaster.cshtml
Notice the BlazorPlaceholder’s(it’s a “Foundation” component, located in Foundation.BlazorExtensions), very Sitecorish no? ๐Ÿ™‚

Remember it’s all client-side here, that means we don’t have to wait for horrible page requests. But we need to know when the user navigates to a page, that is why we will listen to the OnLocationChanged event from the UriHelper(a Blazor helper class). When that happens, we will call the layoutService.LoadRoute(Language) method and get some juicy route data from Sitecore(right now we are using JSON files, like the off-line scenario in JSS).
The JSON files will simulate what the Sitecore Layout Service will return from Sitecore.Here’s what the JSON file looks like for the homepage.
The last thing we need to do is to let the UI know it’s time to re-render the page. We will do that by calling the StateHasChanged() method.

The parameters, Language and PageUrl are route parameters. In a โ€œstandardโ€ Blazor application the router looks for all classes that implement Microsoft.AspNetCore.Blazor.Components.IComponent in the assembly specified in App.cshtml. Each component class has to have a Microsoft.AspNetCore.Blazor.Components.RouteAttribute that specifies the route template. In .cshtml, the attribute is set using @page, something like this:

@page "/testRoute"  

With parameters:

@page "/{Language}/{PageUrl}"

You guys can read more about Routing here, https://learn-blazor.com/pages/router/

But in our case everything is dynamic, that means the routing too. To make it work I need to customize the routing. Instead of adding a @page attribute on the page/s, all routes will be loaded from Sitecore(right now it is mocked), this happens in the App.cshtml

@using Foundation.BlazorExtensions.Services
@using Foundation.BlazorExtensions.CustomBlazorRouter

@inject Microsoft.AspNetCore.Blazor.Services.IUriHelper uriHelper;
@inject Foundation.BlazorExtensions.Services.SitecoreItemService sitecoreItemService;

@*<Router AppAssembly=typeof(Program).Assembly />*@

@*Instead of using default router from Blazor we will have a customized version, which will allow us to add routes*@
<CustomRouter RouteValues="@RoutesData" />

@functions
{

  private RouterDataRoot RoutesData = null;
 
  protected override void OnInit()
  {

    RoutesData = sitecoreItemService.ConfigRoutes();
   
    string relative = uriHelper.ToBaseRelativePath(uriHelper.GetBaseUri(), uriHelper.GetAbsoluteUri());

    if (relative == "")
      uriHelper.NavigateTo("/en");
  }

}

At github: https://github.com/GoranHalvarsson/SitecoreBlazor/blob/master/SitecoreBlazorHosted.Client/App.cshtml

I think I will stop here, there is a lot of stuff to go through ๐Ÿ™‚

I just want you to understand that everything is c# code and it’s all client-side. For instance, take a look at the BlazorPageHeaderCarousel.cshtml view(it’s located in the Feature.PageContent project):

@using Microsoft.AspNetCore.Blazor.Components
@using System.Timers
@using System.Linq
@using Feature.PageContent
@using Foundation.BlazorExtensions.Extensions

@implements IDisposable

<div class="carousel slide" onmouseout="@onmouseout" onmouseover="@onmouseover">

    <ol class="carousel-indicators">
        @for (var i = 0; i < FieldsModel.MultiList("MediaSelector").Value.Values.Count; i++)
        {
            var local_i = i;


            <li class="@IndicatorClassName(i)" onclick="@(evt => NavCarousel(evt,local_i))"></li>
        }
    </ol>


    <div class="carousel-inner">

        @for (int j = 0; j < FieldsModel.MultiList("MediaSelector").Value.Values.Count; j++)
        {

            var field = @FieldsModel.MultiList("MediaSelector").Value.Values[j];

            <div class="@CarouselItemClassName(j)">
                <img class="d-block w-100" src="@field.SitecoreFields.Image("MediaImage").Value.Src" alt="@field.SitecoreFields.Image("MediaImage").Value.Alt">
                <div class="carousel-caption d-none d-md-block bg-dark">

                    <h5>@field.SitecoreFields.PlainText("MediaTitle").Value.HtmlDecode()</h5>
                    <p>@field.SitecoreFields.PlainText("MediaDescription").Value.HtmlDecode()</p>
                </div>
            </div>

        }
    </div>

    <a class="carousel-control-prev" role="button" onclick="@(() => NavCarousel(CarouselDirection.Previous))">
        <span class="carousel-control-prev-icon" aria-hidden="true"></span>
        <span class="sr-only">Previous</span>
    </a>
    <a class="carousel-control-next" role="button" onclick="@(() => NavCarousel(CarouselDirection.Next))">
        <span class="carousel-control-next-icon" aria-hidden="true"></span>
        <span class="sr-only">Next</span>
    </a>

</div>

@functions {

    [Parameter]
    List<SitecoreBlazorHosted.Shared.Models.IBlazorSitecoreField> FieldsModel { get; set; }

    private string IndicatorClassName(int index)
    {
      return ActiveIndex == index ? "active" : null;
    }

    private string CarouselItemClassName(int index)
    {
      var active = ActiveIndex == index ? "active" : null;
      return $"carousel-item {active}";
    }

    private string CarouselItemCaptionClassName
    {
      get
      {
        return $"carousel-caption d-none d-md-block";
      }
    }

    private int _activeIndex;
    int ActiveIndex
    {
      get => _activeIndex;
      set { _activeIndex = value; StateHasChanged(); }
    }

    private Action<int> ActiveIndexChanged { get; set; }


    private int Interval { get; set; } = 5000;

    private Timer _timer;


    protected override void OnInit()
    {
      _timer = new Timer(Interval);
      _timer.Elapsed += OnTimerEvent;
      _timer.AutoReset = true;
      _timer.Start();

    }

    private void OnTimerEvent(Object source, ElapsedEventArgs e)
    {
      if (ActiveIndex == FieldsModel.MultiList("MediaSelector").Value.Values.Count - 1)
        ActiveIndex = 0;
      else
        ActiveIndex = ActiveIndex + 1;

      ActiveIndexChanged(ActiveIndex);
    }


    private void onmouseover()
    {
      if (_timer != null) _timer.Stop();
    }

    private void onmouseout()
    {
      if (_timer != null) _timer.Start();
    }

    private void NavCarousel(CarouselDirection carouselIndication)
    {
      if (carouselIndication == CarouselDirection.Previous)
      {
        if (ActiveIndex == 0)
          ActiveIndex = FieldsModel.MultiList("MediaSelector").Value.Values.Count - 1;
        else
          ActiveIndex = ActiveIndex - 1;
      }
      else
      {
        if (ActiveIndex == FieldsModel.MultiList("MediaSelector").Value.Values.Count - 1)
          ActiveIndex = 0;
        else
          ActiveIndex = ActiveIndex + 1;

      }
    }

    private void NavCarousel(Microsoft.AspNetCore.Blazor.UIMouseEventArgs args, int index)
    {
      ActiveIndex = index;
    }

    public void Dispose()
    {
      if (_timer != null)
      {
        _timer.Stop();
        _timer.Dispose();
        _timer = null;
      }
    }

}

At github: https://github.com/GoranHalvarsson/SitecoreBlazor/blob/master/Feature/PageContent/PageHeaderCarousel/BlazorPageHeaderCarousel.cshtml

Screen dump showing the carousel:

I will continue blogging about the Sitecore + Blazor solution. Stay tuned good people!
Meanwhile look at the code and please, pretty please come with suggestions and improvements.
Code: https://github.com/GoranHalvarsson/SitecoreBlazor
Demo: https://visionsincode.github.io/SitecoreBlazor.io/

It’s a new era people, the BLAZOR era. A lot of interesting stuff is happening in the BLAZOR world,
check out this very nice demo from the BLAZOR team:

Spoiler Alert: Blazor is going native(Electron) and server-side version using WebSockets(signal-r), very cool stuff ๐Ÿ˜‰

Thatโ€™s all for now folks ๐Ÿ™‚


4 thoughts on “Time travel into the future – BLAZOR + SITECORE + HELIX

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.