Blazor + MVC examples

Blazor Server + MVC with Optimizely

Get started with Blazor server + MVC with Optimizely CMS in 10 minutes.

icon of user profile

Published 27th April 2022
Optimizely CMS 12
.net5/6

Blazor server and MVC views works great together. Once you start working with it, you will love it! Blazor server makes it easier for back-end developers to do magic in front end. Give it a test drive, this is how.

Read the conclusion at the end of this blog post.

Install Alloy as Test Template

Open command prompt in new directory

Install the Optimizely .net Templates:

dotnet new --install EPiServer.Templates

Install the the Alloy .net5 Template:
(out of the box the template is .net5 – at time writing)

dotnet new epi-alloy-mvc --name Epicweb.Mvc.Blazor

cmd prompt output

Add Blazor Support

Blazor server is rendered on server and can be combined with MVC.

Add to Startup.cs

services.AddServerSideBlazor();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapContent();
});

(only for versions of CMS < 12.3 – work around)

app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
//var opt = endpoints.ServiceProvider.GetRequiredService<IOptions<StaticFileOptions>>().Value;
//var realprovider = opt.FileProvider;
endpoints.MapContent();
//endpoints.MapRazorPages();
//opt.FileProvider = new CompositeFileProvider(realprovider, opt.FileProvider); //Ensure all providers, both original and Episerver ones, are included (Reported Bug cms12)
});

Add _imports.razor and update _viewimports.cshtml

The purpose of the _ViewImports.cshtml and _imports.razor files are to centralise directives that apply to Views and Razor components so that you don’t have to add them to views individually.

Add _imports.razor into the root folder where you gonna but your razor/blazor components. You can place additional imports files in any subfolder.

@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components
@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

Add to _viewimports.cshtml:

@using Microsoft.AspNetCore.Mvc.Razor
@using Microsoft.AspNetCore.Html

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Add to _root.csthml

in head:

<base href="~/" />

in bottom body:

<script src="~/_framework/blazor.server.js"></script>

Add counter.razor

This is you first blazor component, add to your components or views folder

@using Microsoft.AspNetCore.Components

<p>Current count: @Count</p>

<button @onclick="IncrementCount" class="btn btn-primary">Click me</button>

@code {

[Parameter]
public int Count { get; set; } = 0;

private void IncrementCount()
{
   Count++;
}
}

Now add it to a MVC view

Add the component to a view, _root.cshtml or to a block view

@using Epicweb.Mvc.Blazor.Views
<component type="typeof(Counter)" render-mode="ServerPrerendered"
param-Count="0" />

Run the application

Run the app and register an admin user (the first time)

demo of counter

Server-side Blazor is executed on the server within an ASP.NET Core application. All UI updates, event handling, and JavaScript calls are handled from server by using a SignalR connection, even a button click will go to server but only update this part of the site. No full refresh of page. This is so much fun! You may call javascript from C# or run C# from javascript. And use queryparameters and local/session storage from the browser.

Blazor and code separation / code behind

Yes! it is possible to separate razor views from code with help of partial class. In the gist-examples below this is shown how. This means that you can inherit from a base razor class and have some custom app logic.

Optimizely Blocks and Blazor server

Lets combine Optimizely CMS blocks with blazor server. One thing that bugs me for the moment, is that Blazor components works best with  immutable types, and handles complex types with serialization, meaning it is not passing parameters byref. This means further sending a blocktype or any IContent from MVC to Blazor component will trigger a serialization loop. Simple view models works, but more complex models may be more troublesome.

The block (example 1 – send in int parameter)

Here we just send a “startcount” from the CMS block to the Blazor component:

using System.ComponentModel.DataAnnotations;
using EPiServer;
using EPiServer.DataAbstraction;

namespace Epicweb.Mvc.Blazor.Models.Blocks
{
/// <summary>
/// Used to insert a link which is styled as a button
/// </summary>
[SiteContentType(GUID = "b60386aa-1761-432e-a160-049c18c2815a")]
[SiteImageUrl]
public class CounterBlock : SiteBlockData
{
[Display(Order = 1, GroupName = SystemTabNames.Content)]
[Required]
public virtual int StartCount { get; set; }
}
}

<!-- counterblock.cshtml -->

@using Epicweb.Mvc.Blazor.Views
@model CounterBlock

<h2>@((Model as IContent).Name)</h2>

<component type="typeof(Counter)" render-mode="ServerPrerendered"
param-Count="@Model.StartCount" />

The block (example 2 –  send in ContentReference)

Here we send the block ref to the component and uses contentloader to get the full block

@inject IContentLoader ContentLoader
@code {

public int Count { get; set; } = 0;

[Parameter]
public ContentReference BlockID { get; set; }
public CounterBlock CurrentBlock { get
{
if (BlockID == null)
return null;
return ContentLoader.Get<CounterBlock>(BlockID);
}
}

protected override void OnInitialized()
{
Count = CurrentBlock.StartCount;
base.OnInitialized();
}

private void IncrementCount()
{
Count++;
}
}
<component type="typeof(Counter)" render-mode="ServerPrerendered" 
param-BlockID="@(Model as IContent).ContentLink" />

More Code Samples Below

Conclusion

  • Blazor server side and MVC works together
  • Complex types can be troublesome to pass between MVC and Blazor Components
    • Not possible to send blockdata as param without a custom JsonConverter
  • Do logic direct in Blazor code instead of Block Controller/Components
  • Send ContentReference to block as param