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
- Install the .NET Core 2.1 SDK (2.1.300 or later).
- Install Visual Studio 2017 Preview with the ASP.NET and web development workload selected.
- 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; } } }
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 ๐
This UriHelper.OnLocationChanged event has new parameters that were causing an error with this example with .netCore 3 preview6. Should be: private void OnLocationChanges(object sender, LocationChangedEventArgs e) Thanks. I am using it in my Blazor Boilerplate: https://github.com/enkodellc/blazorboilerplate
LikeLiked by 1 person
Hello, thanks for reading. My blog-post is a bit old. Great that you point it out ๐ I keep my solution updated at github, https://github.com/GoranHalvarsson/SitecoreBlazor.
I have also added a server-side version, please check it out
LikeLike