Blazor Server + MVC with Optimizely
Get started with Blazor server + MVC with Optimizely CMS in 10 minutes.
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
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)
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